This project is a small, from-scratch vector store built in plain Node.js.
It is intentionally simple:
text-embedding-3-small / text-embedding-3-large).This is a teaching system, not a production semantic search engine.
If you want the smallest real training loop in this repo, run:
npm run train:simple
That example lives in src/trainSimpleModel.js and is explained in docs/train-simple-model.md.
It trains a tiny linear model from scratch using:
This is the best first step before moving to logistic regression or neural nets.
If you do not yet understand vector databases, this project lets you see the whole pipeline:
Once this clicks, you can swap the toy embedder for a real embedding model and the JSON file for Pinecone, Postgres + pgvector, Qdrant, or OpenAI vector stores.
npm run demo
That command will:
data/vector-store.jsonAfter that, try your own queries:
npm run search -- "how long do refunds take"
Or filter by metadata:
npm run search -- "can I use SSO" product=enterprise
You can also choose a retrieval mode:
npm run search -- mode=vector "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=bm25 "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=hybrid "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=hybrid topK=5 vectorK=3 bm25K=2 "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=rerank topK=3 vectorK=3 bm25K=2 candidateTopK=5 "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=rerank topK=5 vectorK=10 bm25K=10 candidateTopK=10 finalVectorMin=2 finalBm25Min=1 "Multer"
npm run search -- mode=answer topK=5 vectorK=10 bm25K=10 candidateTopK=10 finalVectorMin=2 finalBm25Min=1 "Miten valokuvat prosessoidaan nakkilistassa?"
For mode=answer, set OPENAI_API_KEY first. The CLI will:
Sources section built from the cited chunk IDsYou can put the key in a local .env file:
OPENAI_API_KEY=your-key
Or set it directly in PowerShell:
$env:OPENAI_API_KEY="your-key"
npm run search -- mode=answer topK=5 vectorK=10 bm25K=10 candidateTopK=10 "Miten valokuvat prosessoidaan nakkilistassa?"
Start the browser workbench:
npm run ui
Then open:
http://127.0.0.1:4173
The UI lets you:
vector, bm25, hybrid, rerank, or answer modeSources for answer modedata/vector-store.json without restarting the serverThe browser UI uses the same query pipeline as the CLI, so the results should match npm run search.
sourcesPut a PDF in sources and optionally add a meta.txt file with tab-separated metadata rows.
Then run:
npm run index:sources
Or build a real embedding index with OpenAI:
npm run index:sources:openai
That uses text-embedding-3-small by default. You can also override it:
npm run index:sources -- embedder=openai embeddingModel=text-embedding-3-large
npm run index:sources -- embedder=openai embeddingModel=text-embedding-3-small dimensions=1024
That command will:
sourcesmeta.txtsourceTitle, sourceAuthor, sourceLanguage, and sourceSubjectsdata/vector-store.jsonExample searches:
npm run search -- "Mille alustoille prototyyppi tehtiin?"
npm run search -- "Kuka on työn tekijä?" sourceAuthor="Mäki, Jussi"
npm run search -- mode=bm25 "Multer"
npm run search -- mode=rerank topK=3 vectorK=3 bm25K=2 candidateTopK=5 "Multer"
The search output now shows which embedder was used for the loaded store. If the store was indexed with OpenAI embeddings, query-time vector search will call the OpenAI embeddings API to embed the user query before comparing it to stored chunk vectors.
A small thesis eval set lives at evals/thesis-eval.json.
Run retrieval-focused evals:
npm run eval -- mode=vector
npm run eval -- mode=bm25
npm run eval -- mode=hybrid
npm run eval -- mode=rerank
npm run eval -- mode=compare
Run end-to-end answer evals:
npm run eval -- mode=answer
npm run eval -- mode=all
The eval runner reports:
Hit@1, Hit@3, Hit@5, and MRR@K for retrieval when availablemode=compare, side-by-side retrieval metrics for vector, bm25, hybrid, and rerankmode=answer, a heuristic answer score based on:
The embedder is local and deterministic. It uses token hashing, not a real semantic embedding model.
That means:
5 commits
JavaScript
86.0%
HTML
7.4%
CSS
6.5%
This project is a small, from-scratch vector store built in plain Node.js.
It is intentionally simple:
text-embedding-3-small / text-embedding-3-large).This is a teaching system, not a production semantic search engine.
If you want the smallest real training loop in this repo, run:
npm run train:simple
That example lives in src/trainSimpleModel.js and is explained in docs/train-simple-model.md.
It trains a tiny linear model from scratch using:
This is the best first step before moving to logistic regression or neural nets.
If you do not yet understand vector databases, this project lets you see the whole pipeline:
Once this clicks, you can swap the toy embedder for a real embedding model and the JSON file for Pinecone, Postgres + pgvector, Qdrant, or OpenAI vector stores.
npm run demo
That command will:
data/vector-store.jsonAfter that, try your own queries:
npm run search -- "how long do refunds take"
Or filter by metadata:
npm run search -- "can I use SSO" product=enterprise
You can also choose a retrieval mode:
npm run search -- mode=vector "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=bm25 "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=hybrid "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=hybrid topK=5 vectorK=3 bm25K=2 "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=rerank topK=3 vectorK=3 bm25K=2 candidateTopK=5 "Mille alustoille prototyyppi tehtiin?"
npm run search -- mode=rerank topK=5 vectorK=10 bm25K=10 candidateTopK=10 finalVectorMin=2 finalBm25Min=1 "Multer"
npm run search -- mode=answer topK=5 vectorK=10 bm25K=10 candidateTopK=10 finalVectorMin=2 finalBm25Min=1 "Miten valokuvat prosessoidaan nakkilistassa?"
For mode=answer, set OPENAI_API_KEY first. The CLI will:
Sources section built from the cited chunk IDsYou can put the key in a local .env file:
OPENAI_API_KEY=your-key
Or set it directly in PowerShell:
$env:OPENAI_API_KEY="your-key"
npm run search -- mode=answer topK=5 vectorK=10 bm25K=10 candidateTopK=10 "Miten valokuvat prosessoidaan nakkilistassa?"
Start the browser workbench:
npm run ui
Then open:
http://127.0.0.1:4173
The UI lets you:
vector, bm25, hybrid, rerank, or answer modeSources for answer modedata/vector-store.json without restarting the serverThe browser UI uses the same query pipeline as the CLI, so the results should match npm run search.
sourcesPut a PDF in sources and optionally add a meta.txt file with tab-separated metadata rows.
Then run:
npm run index:sources
Or build a real embedding index with OpenAI:
npm run index:sources:openai
That uses text-embedding-3-small by default. You can also override it:
npm run index:sources -- embedder=openai embeddingModel=text-embedding-3-large
npm run index:sources -- embedder=openai embeddingModel=text-embedding-3-small dimensions=1024
That command will:
sourcesmeta.txtsourceTitle, sourceAuthor, sourceLanguage, and sourceSubjectsdata/vector-store.jsonExample searches:
npm run search -- "Mille alustoille prototyyppi tehtiin?"
npm run search -- "Kuka on työn tekijä?" sourceAuthor="Mäki, Jussi"
npm run search -- mode=bm25 "Multer"
npm run search -- mode=rerank topK=3 vectorK=3 bm25K=2 candidateTopK=5 "Multer"
The search output now shows which embedder was used for the loaded store. If the store was indexed with OpenAI embeddings, query-time vector search will call the OpenAI embeddings API to embed the user query before comparing it to stored chunk vectors.
A small thesis eval set lives at evals/thesis-eval.json.
Run retrieval-focused evals:
npm run eval -- mode=vector
npm run eval -- mode=bm25
npm run eval -- mode=hybrid
npm run eval -- mode=rerank
npm run eval -- mode=compare
Run end-to-end answer evals:
npm run eval -- mode=answer
npm run eval -- mode=all
The eval runner reports:
Hit@1, Hit@3, Hit@5, and MRR@K for retrieval when availablemode=compare, side-by-side retrieval metrics for vector, bm25, hybrid, and rerankmode=answer, a heuristic answer score based on:
The embedder is local and deterministic. It uses token hashing, not a real semantic embedding model.
That means:
5 commits
JavaScript
86.0%
HTML
7.4%
CSS
6.5%