@aiconnect/easy-rag
v2.4.2
Published
A TypeScript CLI tool for local RAG indexing and querying
Maintainers
Readme
@aiconnect/easy-rag
A TypeScript CLI for local RAG (Retrieval-Augmented Generation) — index documents and query them with natural language.
Uses OpenAI embeddings and Zvec (embedded vector database) for storage. No external infrastructure or server needed.
Quick Start
Prerequisites: Node.js >= 18 and an OpenAI API key.
# Install
npm install -g @aiconnect/easy-rag
# Set up your API key (interactive)
easy-rag init
# Index a folder and query it
easy-rag index ./my-docs
easy-rag query "What is the refund policy?"Example output:
$ easy-rag index ./my-docs
Scanning ./my-docs...
Found 3 files (2 .md, 1 .pdf)
Chunking documents... 12 chunks created
Generating embeddings... done
Indexed 12 chunks into collection "my-docs"
$ easy-rag query "What is the refund policy?"
Result 1:
Refunds are available within 30 days of purchase.
Contact [email protected] to initiate a refund request.
Result 2:
All subscription plans include a 30-day money-back guarantee...Commands
easy-rag init
Interactive setup — prompts for your OpenAI API key, embedding model, and advanced search settings. Saves to $XDG_CONFIG_HOME/easy-rag/config.json (default ~/.config/easy-rag/config.json).
Basic Setup:
- OpenAI API key
- Embedding model (text-embedding-3-large, -small, or -ada-002)
Advanced Settings (optional):
- Search mode: auto, hybrid, vector, or bm25 (default: auto)
- Vector weight: 0.0-1.0 (default: 0.6)
- BM25 weight: 0.0-1.0 (default: 0.4)
You can also skip init and set an environment variable instead:
export OPENAI_API_KEY="sk-..."easy-rag index <folder>
Scans a folder recursively and indexes all supported files into Zvec.
easy-rag index ./knowledge-base- Supported formats:
.pdf,.md,.csv - Chunking: Automatic — Markdown by heading, PDF by paragraph, CSV by row
- Collection name: Derived from the folder name
- Re-indexing the same folder creates a new collection with a unique suffix
easy-rag query <question>
Search indexed documents using auto mode (RRF fusion) by default.
easy-rag query "How do I configure API?"
easy-rag query --top 3 --metadata "quarterly revenue"
easy-rag query --collection my-docs "setup instructions"
easy-rag query --mode vector "semantic search only"
easy-rag query --mode bm25 "exact keyword match"| Option | Description | Default |
|--------|-------------|---------|
| -t, --top <n> | Number of results | 5 |
| -m, --metadata | Show source file, score, chunk index | off |
| -c, --collection <name> | Search specific collection | all |
| --mode <mode> | Search mode: auto, hybrid, vector, or bm25 | auto |
easy-rag collections
List all indexed collections with document counts.
easy-rag delete <collection>
Delete a collection. Use --force to skip confirmation.
easy-rag delete my-docs --forceeasy-rag parse <file>
Parse a single file and print extracted text. Useful for debugging.
How It Works
Documents (.pdf, .md, .csv)
│
▼
┌─────────┐ ┌─────────┐ ┌──────────────┐ ┌────────┐
│ Parse │ → │ Chunk │ → │ Embed │ → │ Store │
│ │ │ │ │ (OpenAI) │ │ (Zvec) │
└─────────┘ └─────────┘ └──────────────┘ └────────┘
│ │
│ Dense + Sparse vectors
│
Query ─── Embed ───┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Dense Search Sparse Search RRF Merge
│ │ │
└───────────────┼───────────────┘
▼
Relevant chunks- Parse — Extract text from PDF, Markdown, or CSV
- Chunk — Split intelligently by heading, paragraph, or row
- Embed — Generate dense vectors via OpenAI (
text-embedding-3-large) + sparse vectors (TF-hash) - Store — Save in Zvec (embedded, no server needed)
- Query — Embed the question and find the closest chunks via hybrid search
Hybrid Search
EasyRAG uses hybrid search combining dense vector similarity and sparse keyword matching via Reciprocal Rank Fusion (RRF).
Why Hybrid Search?
- Dense search captures semantic meaning but can miss exact keywords
- Sparse search excels at keyword matches but lacks semantic understanding
- Hybrid search gives the best of both worlds — like production RAG systems
How It Works
score(d) = vector_weight / (60 + vector_rank) + bm25_weight / (60 + bm25_rank)Search Modes
| Mode | Description | Use Case |
|------|-------------|----------|
| auto (default) | Dense + Sparse via RRF, no weights needed | General queries |
| hybrid | Dense + Sparse with configurable weights | Fine-tuned relevance |
| vector | Dense vector similarity only | Semantic/abstract queries |
| bm25 | Sparse keyword only | Exact terms, acronyms, names |
In hybrid mode, weights are configurable: vector=0.6, bm25=0.4 (via config.json).
In auto mode, weights are fixed at 0.5/0.5 (RRF with equal weights, no configuration needed).
Note:
autocurrently uses manual RRF fusion because the Zvec Node SDK (@zvec/zvec) does not yet expose native multi-vector queries or rerankers. When the SDK adds this support,autowill delegate fusion to the engine.
Example:
# Auto search (default — RRF fusion, no config needed)
easy-rag query "refund policy"
# Hybrid with custom weights from config
easy-rag query --mode hybrid "refund policy"
# Vector-only for semantic queries
easy-rag query --mode vector "how does it work?"
# BM25-only for exact keywords
easy-rag query --mode bm25 "API endpoint /v1/users"Configuration
API Key
| Method | How | Priority |
|--------|-----|----------|
| easy-rag init | Interactive prompt, saved to $XDG_CONFIG_HOME/easy-rag/config.json (default ~/.config/easy-rag/config.json) | 2nd |
| Environment variable | export OPENAI_API_KEY="sk-..." | 1st (overrides config) |
Embedding Model
Set during easy-rag init or via EMBEDDING_MODEL env var.
| Model | Notes |
|-------|-------|
| text-embedding-3-large | Default, best performance |
| text-embedding-3-small | Faster, cheaper |
| text-embedding-ada-002 | Legacy |
Advanced Search Settings
Configure during easy-rag init or edit $XDG_CONFIG_HOME/easy-rag/config.json (default ~/.config/easy-rag/config.json).
| Setting | Description | Default |
|---------|-------------|---------|
| search_mode | Search mode: auto, hybrid, vector, or bm25 | auto |
| vector_weight | Weight for vector results in hybrid mode (0.0-1.0) | 0.6 |
| bm25_weight | Weight for sparse results in hybrid mode (0.0-1.0) | 0.4 |
Weights don't need to sum to 1.0 — they are used independently in the RRF formula.
Zvec
Zvec is the embedded vector store — no server or external process needed.
- Data is stored in
$XDG_DATA_HOME/easy-rag/collections/(default~/.local/share/easy-rag/collections/), auto-created on first use - Each collection is a directory on disk, fully portable
Storage Paths (XDG Base Directory Specification)
EasyRAG follows the XDG Base Directory Specification on every platform (Linux and macOS alike — no ~/Library/Application Support special-case).
| Resource | Path | Override |
|----------|------|----------|
| Config (config.json) | ~/.config/easy-rag/ | XDG_CONFIG_HOME (must be an absolute path) |
| Data (collections/) | ~/.local/share/easy-rag/ | XDG_DATA_HOME (must be an absolute path) |
If XDG_CONFIG_HOME / XDG_DATA_HOME are unset, empty, whitespace-only, or set to a relative path, EasyRAG falls back to the defaults above.
Library Usage (Programmatic API)
Easy RAG can also be used as a library in Node.js applications (Express.js, Fastify, etc.):
import { EasyRAG } from '@aiconnect/easy-rag';
const rag = new EasyRAG({
apiKey: process.env.OPENAI_API_KEY,
collectionsDir: '/data/collections',
readOnly: true,
});
const results = await rag.query('What is the refund policy?', {
collection: 'docs',
top: 5,
mode: 'auto',
});
for (const result of results) {
console.log(result.content);
console.log(`Score: ${result.hybridScore.toFixed(4)}`);
}
await rag.close();EasyRAGConfig
| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| collectionsDir | string | Yes | — | Path to collections directory |
| apiKey | string | No | OPENAI_API_KEY env var | OpenAI API key |
| model | string | No | text-embedding-3-large | Embedding model |
| readOnly | boolean | No | true | Open collections read-only |
| logger | EasyRAGLogger | No | Silent (no output) | Custom logger |
| vectorWeight | number | No | 0.6 | RRF vector weight |
| bm25Weight | number | No | 0.4 | RRF BM25 weight |
EasyRAG Methods
| Method | Returns | Description |
|--------|---------|-------------|
| query(question, options?) | Promise<HybridQueryResult[]> | Query a collection |
| listCollections() | CollectionInfo[] | List loaded collections |
| hasCollection(name) | boolean | Check if collection exists |
| close() | void | Close all collections (idempotent) |
QueryRequestOptions
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| collection | string | — | Collection name (required for query) |
| top | number | 5 | Number of results |
| mode | 'auto' \| 'hybrid' \| 'vector' \| 'bm25' | 'auto' | Search mode |
Express.js Integration Example
import express from 'express';
import { EasyRAG } from '@aiconnect/easy-rag';
const app = express();
app.use(express.json());
const rag = new EasyRAG({
apiKey: process.env.OPENAI_API_KEY,
collectionsDir: process.env.COLLECTIONS_DIR!,
readOnly: true,
});
app.get('/query', async (req, res) => {
try {
const { q, collection = 'docs', top = 5 } = req.query;
const results = await rag.query(q as string, {
collection: collection as string,
top: Number(top),
});
res.json(results);
} catch (error: any) {
const status = error.message?.includes('not found') ? 404 : 500;
res.status(status).json({ error: error.message });
}
});
app.listen(3000, () => console.log('RAG API on :3000'));
process.on('SIGTERM', () => { rag.close(); process.exit(0); });Logger Injection
By default, the library is silent (no console output). Inject a custom logger:
import { EasyRAG, type EasyRAGLogger } from '@aiconnect/easy-rag';
const logger: EasyRAGLogger = {
info: (msg) => myLogger.info(msg),
warn: (msg) => myLogger.warn(msg),
error: (msg) => myLogger.error(msg),
};
const rag = new EasyRAG({ collectionsDir: '/data', logger });Use consoleLogger for CLI-like output:
import { EasyRAG, consoleLogger } from '@aiconnect/easy-rag';
const rag = new EasyRAG({ collectionsDir: '/data', logger: consoleLogger });Development
git clone https://github.com/johnjohn-aic/easy-rag.git
cd easy-rag
npm install
npm run build # Compile TypeScript
npm test # Run tests
npx tsx src/cli.ts <command> # Dev mode (no build needed)Project Structure
src/
cli.ts # CLI entrypoint (commander, shebang)
index.ts # Library API exports
lib/ # Programmatic API (EasyRAG class)
parsers/ # PDF, Markdown, CSV text extraction
chunker/ # Content-aware text splitting
embeddings/ # OpenAI embedding generation
vector-store/ # Zvec embedded vector store + sparse tokenizer
query/ # Dense, sparse, and hybrid search
indexer/ # Orchestrates parse → chunk → embed → store
config/ # Global config (XDG_CONFIG_HOME/easy-rag/config.json)
paths/ # XDG-compliant config/data path resolution
commands/ # CLI command implementations (init, serve)License
MIT
