Lightweight retrieval reranker for JavaScript and TypeScript -- zero dependencies, three scoring modes, one API.
rerank-lite takes a query and a list of candidate documents from a first-stage retriever (vector search, BM25, or fusion) and reorders them by relevance. It is the second stage of a two-stage retrieval pipeline: stage one retrieves candidates quickly with approximate methods, stage two reranks them precisely for the final result set.
The package ships three scoring modes that share a single rerank() API and return the same RerankResult[] output:
- Heuristic -- BM25 + TF-IDF + position-based scoring with zero external dependencies. Works immediately after install.
- LLM -- Delegates scoring to a caller-supplied judge function. Bring any LLM provider (OpenAI, Anthropic, local Ollama, etc.).
- Hybrid -- A 50/50 blend of heuristic and LLM scores for balanced accuracy and cost.
All scores are normalized to the [0, 1] range. The output is ready to pass to a context packer, fusion ranker, or directly into an LLM prompt.
npm install rerank-liteRequires Node.js 18 or later.
import{rerank}from'rerank-lite'constdocuments=[{id: 'doc-0',text: 'Machine learning model training with neural networks'},{id: 'doc-1',text: 'Quick sort algorithm and data structures'},{id: 'doc-2',text: 'Deep learning transformer architecture for NLP'},]constresults=awaitrerank('machine learning neural network',documents)for(constresultofresults){console.log(`[rank ${result.newRank}] ${result.document.id} -- score: ${result.score.toFixed(4)}`)}- Three scoring modes -- heuristic, LLM-as-judge, and hybrid, all behind one function signature.
- Zero runtime dependencies -- heuristic mode is pure TypeScript with no external packages.
- BM25 scoring -- corpus-aware term frequency / inverse document frequency with configurable k1 and b parameters, normalized via sigmoid.
- TF-IDF cosine similarity -- builds TF-IDF vectors for the query and each document, scores by cosine similarity against the corpus.
- Position bias -- original retrieval rank feeds into the composite score, preserving signal from the first-stage retriever.
- Configurable weights -- tune the relative contribution of BM25, TF-IDF, and position scoring.
- Top-K and minimum score filtering -- limit output to the top K results and filter below a score threshold.
- Pluggable LLM judge -- pass any async function that scores a (query, document) pair. No SDK lock-in.
- Factory pattern --
createReranker()returns a preconfigured instance for repeated use across many queries. - Full TypeScript support -- ships with declaration files and source maps. All types are exported.
Reranks an array of documents against a query. Returns a Promise<RerankResult[]> sorted by relevance score descending.
import{rerank}from'rerank-lite'constresults=awaitrerank('machine learning',documents,{mode: 'heuristic',topK: 10,minScore: 0.3,weights: {bm25: 0.5,tfidf: 0.3,position: 0.2},})Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | Yes | The search query to rank documents against. |
documents | Document[] | Yes | Array of candidate documents from a first-stage retriever. |
options | RerankOptions | No | Configuration for scoring mode, filtering, and weights. |
Returns:Promise<RerankResult[]> -- results sorted by score descending, with newRank assigned as 0-based indices.
Behavior with empty input: Returns an empty array when documents is empty.
Factory that returns a configured Reranker instance. Preset options are applied to every rerank() call; per-call options override the preset.
import{createReranker}from'rerank-lite'constreranker=createReranker({mode: 'heuristic',topK: 10,weights: {bm25: 0.6,tfidf: 0.3,position: 0.1},})constresults=awaitreranker.rerank('first query',docs)// Override topK for this call onlyconsttop3=awaitreranker.rerank('second query',docs,{topK: 3})Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
config | RerankerConfig | No | Default configuration applied to all calls. |
Returns:Reranker -- an object with a rerank() method and a read-only config property.
Represents a candidate document to be scored.
interfaceDocument{id: string// Unique identifier for the documenttext: string// The text content to score against the queryscore?: number// Optional original retrieval scoremetadata?: Record<string,unknown>// Optional pass-through metadata}A scored and ranked document returned by rerank().
interfaceRerankResult{document: Document// The original document object, including metadatascore: number// Relevance score in [0, 1]originalRank: number// 0-based index in the input arraynewRank: number// 0-based rank after reranking (0 = most relevant)explanation?: string// Optional debug information}Options for a single rerank() call.
interfaceRerankOptions{topK?: number// Return only the top K results (default: all)minScore?: number// Filter out results with a score below this thresholdmode?: RerankMode// Scoring mode (default: 'heuristic')judgeFn?: JudgeFn// Required when mode is 'llm' or 'hybrid'weights?: ScoringWeights// Custom weights for heuristic scoring components}typeRerankMode='heuristic'|'llm'|'hybrid'| Mode | Description |
|---|---|
heuristic | BM25 + TF-IDF + position weighted scoring. Zero dependencies. Default. |
llm | Delegates scoring entirely to the caller-supplied judgeFn. |
hybrid | 50/50 blend of heuristic scores and judgeFn scores. |
The function signature for LLM-as-judge scoring.
typeJudgeFn=(query: string,document: string)=>Promise<number>Must return a numeric relevance score. Called once per document when mode is llm or hybrid.
Weights for the three heuristic scoring signals. All are optional and default to the values shown.
interfaceScoringWeights{bm25?: number// Default: 0.5tfidf?: number// Default: 0.3position?: number// Default: 0.2}Configuration for the createReranker() factory. Same shape as RerankOptions.
interfaceRerankerConfig{mode?: RerankModetopK?: numberminScore?: numberjudgeFn?: JudgeFnweights?: ScoringWeights}The instance returned by createReranker().
interfaceReranker{rerank(query: string,documents: Document[],options?: RerankOptions): Promise<RerankResult[]>readonlyconfig: RerankerConfig}The heuristic composite score is computed as:
score = (bm25_weight * bm25_score) + (tfidf_weight * tfidf_score) + (position_weight * position_score)
Default weights:
| Signal | Default Weight | Description |
|---|---|---|
bm25 | 0.5 | BM25 term frequency / inverse document frequency score, normalized to [0, 1] via sigmoid. |
tfidf | 0.3 | TF-IDF cosine similarity between query and document vectors. |
position | 0.2 | Position bias: 1 - (originalRank / totalDocuments). Preserves signal from the first-stage retriever. |
Override weights to tune for your retrieval domain:
// Favor lexical match, ignore original orderingconstresults=awaitrerank('exact keyword query',docs,{weights: {bm25: 0.7,tfidf: 0.3,position: 0.0},})// Trust the first-stage retriever, use reranking as a tiebreakerconstresults=awaitrerank('semantic query',docs,{weights: {bm25: 0.2,tfidf: 0.2,position: 0.6},})The BM25 scorer accepts standard tuning parameters k1 (term frequency saturation, default 1.5) and b (document length normalization, default 0.75). These are set internally and follow the Robertson/Zaragoza BM25 formulation.
rerank() handles edge cases gracefully without throwing:
- Empty documents array -- returns an empty array
[]. - No matching terms -- all documents receive a baseline score (sigmoid of zero for BM25, zero for TF-IDF). Documents are still ranked by position bias.
- Empty query or document text -- TF-IDF returns 0 for empty strings. BM25 returns the sigmoid baseline (0.5).
When using llm or hybrid mode, errors from the judgeFn propagate as-is. Wrap your judge function with try/catch if you need graceful degradation:
constsafejudgeFn=async(query: string,doc: string): Promise<number>=>{try{returnawaityourLLMScorer(query,doc)}catch{return0// Fallback score on failure}}constresults=awaitrerank('query',docs,{mode: 'llm',judgeFn: safejudgeFn})Pass any async function that returns a relevance score. The function is called once per document.
import{rerank}from'rerank-lite'importOpenAIfrom'openai'constopenai=newOpenAI()constjudgeFn=async(query: string,document: string): Promise<number>=>{constresponse=awaitopenai.chat.completions.create({model: 'gpt-4o-mini',messages: [{role: 'user',content: `Rate the relevance of this document to the query on a scale from 0 to 1.\nQuery: ${query}\nDocument: ${document}\nRespond with only a number between 0 and 1.`,},],})returnparseFloat(response.choices[0].message.content??'0')}constresults=awaitrerank('machine learning',docs,{mode: 'llm', judgeFn })Combines heuristic scoring with LLM judgment. The final score is a 50/50 blend:
hybrid_score = 0.5 * heuristic_score + 0.5 * llm_score
constresults=awaitrerank('machine learning',docs,{mode: 'hybrid',
judgeFn,weights: {bm25: 0.5,tfidf: 0.3,position: 0.2},// Applies to heuristic half})Use reranking as stage two after vector search or BM25 retrieval:
// Stage 1: Retrieve candidates from your vector databaseconstcandidates=awaitvectorDB.query(embedding,{topK: 50})// Stage 2: Rerank for precisionconstreranked=awaitrerank(query,candidates,{topK: 10,minScore: 0.4})// Stage 3: Pass to LLM contextconstcontext=reranked.map(r=>r.document.text).join('\n\n')For applications that rerank many queries with the same configuration:
import{createReranker}from'rerank-lite'constreranker=createReranker({mode: 'heuristic',topK: 10,minScore: 0.3,weights: {bm25: 0.6,tfidf: 0.25,position: 0.15},})// Use across multiple queriesconstresults1=awaitreranker.rerank('first query',docs1)constresults2=awaitreranker.rerank('second query',docs2)// Override per-call when neededconstresults3=awaitreranker.rerank('third query',docs3,{topK: 5})minScore is applied first, then topK limits the output:
// Return at most 5 results, all with score >= 0.5constresults=awaitrerank('query',docs,{topK: 5,minScore: 0.5})rerank-lite is written in TypeScript and ships with declaration files (.d.ts) and source maps. All types are exported from the package entry point:
import{rerank,createReranker,typeDocument,typeRerankResult,typeRerankOptions,typeRerankMode,typeJudgeFn,typeScoringWeights,typeRerankerConfig,typeReranker,}from'rerank-lite'The package targets ES2022 and emits CommonJS modules. It is compatible with both require() and bundlers that resolve the exports field.
MIT