Hybrid search that finds the exact symbol and what you meant

with weighted rank fusion with exact matching for symbols and IDs with an optional ColBERT reranker that skips the reranker when sources agree in browsers, Bun and Node.js measured in 20 languages

defuss-search runs a lexical and a vector retriever over the same document IDs and fuses their rankings. Exact promotion keeps a document whose symbol or ID equals the query on top, weighted rank fusion orders everything after them, and an optional ColBERT stage reorders the top 25. Every result carries the ranks and scores behind its position.

Search algorithms and engineering brought to you by Aron Homberg (kyr0) (opens in a new tab) · Open Source (MIT) · built on defuss-lexicalsearch (opens in a new tab) and defuss-vectorsearch (opens in a new tab)

Exact where it matters

Symbols, IDs and error codes need exact matches. With exact: 'promote', a document whose exact field equals the query ranks first, even when the query vector points at another one. Stemmed fields get an unstemmed companion, so a word as written beats words that only share its stem.

Fusion or a cascade

Fuse independent lexical and vector top-25 lists with weighted reciprocal rank fusion, or rescore one source's candidates with the other: fusion, vector-first or lexical-first, chosen per query.

Neural only where it helps

searchPreset('reranked') lets ColBERT reorder the top 25 and skips the model when the vector winner already ranks first by a clear margin and lexical retrieval agrees. searchPreset('efficient') runs no reranker at all.

Every rank explained

Each hit reports its rank and raw score in every source, each fusion contribution and the reasons for a promotion. Each response says whether the gate ran or skipped the reranker, and why.

Small core, optional peers

The root entry has no runtime dependencies and ships as ESM and CommonJS with TypeScript types. Adapters for defuss-lexicalsearch, defuss-vectorsearch and ColBERT load only when you import them; your own retrievers and rerankers plug in the same way.

20 languages, measured

Per-language analyzers from defuss-lexicalsearch and multilingual Winzling vectors cover 20 languages. The benchmark below reports R@1 to R@25 for each, with the same ranks on Bun, Node.js and Chromium.

Retrieve.

Fuse.

Rerank.

Live demo

Pick a factory, then search

The recall benchmark's 2,000 passages in 20 languages, indexed in your browser by the factory you choose. Pick one of the 1,000 benchmark questions with ⌘K and watch where it ranks the gold passage, or type your own question. In the tab you build a hybrid index of your own notes.

Choose a search factory

Each factory of defuss-search/strategies needs a different download. The figures are R@1 / R@5 from bun run bench:strategies. The API docs are defuss-shadcn's 8,043 documentation sections, indexed with unstemmed companion fields, which took exact words from 71% to 98% R@1. The benchmark corpus is loading meanwhile.

Search factory
How it works

Retrieve twice, fuse once, rerank only when needed

The lexical retriever gets the query text, the vector retriever its embedding; both return their top 25 within the same eligible IDs. Pure functions fuse the two rankings and apply exact promotion and source coverage. The gate then decides whether ColBERT reorders the top 25, and the policies apply again before the page is cut.

Lexical · defuss-lexicalsearch
Vector · defuss-vectorsearch
01 · Query
02 · Retrieve
03 · Fuse and gate
04 · Rerank and rank
Query text words · symbols · IDs
Query vector same model as the documents
BM25 · exact fields Lexical top 25 analyzer per language
Cosine similarity Vector top 25 exact or TurboQuant scan
Weighted RRF · k=1 · 3:1 Fuse and promote exact promotion · coverage
Reranked preset Agreement gate vector margin at least 0.1 · lexical agrees
MoEdernColBERT Rerank top 25 neural weight 0.25
Final ranking ranks · scores · reasons
  1. text
  2. vector
  3. ranks
  4. ranks
  5. top 25
  6. rerank
  7. reorder
  8. skip

Fusion default

Retrieve both sources independently, deduplicate the union and combine the ranks. A document that only one source finds can still win.

Vector first

Retrieve semantic candidates, then order that pool with the lexical index. Candidates without a lexical hit stay in the tail.

Lexical first

Retrieve lexical candidates, then rescore those IDs by vector similarity. The second stage cannot recover a candidate the first one missed.

The ranking contract, in order

Final rank is authoritative; scores from different stages are not comparable, so each hit states its scoreKind.

  1. retrieve each source returns its top 25 among the eligible IDs
  2. fuse weighted reciprocal rank fusion, or the second stage of a cascade
  3. exact with exact: 'promote', literal matches move to the front
  4. coverage balance reserves early ranks for each source's best candidate
  5. gate a clear vector winner that lexical retrieval agrees with skips the reranker
  6. rerank ColBERT reorders the top 25; exact promotion and coverage apply again
  7. page ranks are assigned before the page is cut

A measured frontier, an explicit choice

Fusion weights, RRF constants and reranker gates were chosen on 500 development questions and then scored once on 500 held-out questions in 20 languages. The table lists the configurations that span quality against inference work; the presets ship the chosen ones. Gated neural counts replay real ColBERT scores and are not latency measurements. Methodology and limits.

held-out R@1 with the efficient preset, no reranker
held-out R@5 with the reranked preset, the demo default
held-out questions that still need ColBERT with the reranked preset
R@5 of lexical search alone, the cheapest point on the frontier

Held-out quality and work

500 questions; vector and neural columns count the questions that needed each model.

Configuration R@1 R@5 Candidates Vector Neural

Measured, not promised*

Each of the 2,000 passages of tiny-embedding-bench-v1 (opens in a new tab) is indexed once, and 1,000 questions in 20 languages search them. R@k is the share of a language's 50 questions whose gold passage, in the question's language, ranks in the top k. Lexical search knows the question's language; vector search scans every passage, so a question also competes with the 19 translations of its gold passage. From make bench, version 0.4.0.

91.3%mean R@1 over 20 languages with the efficient preset, no neural inference
97.3%mean R@5; the gold passage is among the first five results
885of 1,000 questions need ColBERT inference with the reranked preset, for a mean R@5 of 97.4%
6 of 20languages reach 100% R@5 with the efficient preset: English, German, French, Italian, Russian and Indonesian

* Runtime comparison: Bun 1.4.3, Node.js 26.11.1 and Chromium 156.0.8078.4 returned the same rank for each of the 1,000 questions under both presets. Word segmentation for Japanese, Chinese and Thai follows the runtime's ICU data (Bun 78.1, Node.js 78.3).

Recall by query language

Share of a language's 50 questions whose gold passage ranks in the top k of 2,000; one question is 2 points. The badge names how the language's lexical analyzer works. Reranking reorders only the top 25, so R@25 is the same for both presets.

searchPreset('efficient'): lexical and vector top 25, weighted RRF with k=1 and weights 3:1, no neural stage; when BM25's first hit matches few or only common query words and trails the vector first hit, that vector hit leads. Mean over 20 languages: R@1 91.3% · R@5 97.3% · R@10 98.5% · R@25 99.0%.

English Snowball100%
German Snowball100%
French Snowball100%
Italian Snowball100%
Russian Snowball100%
Indonesian Snowball100%
Spanish Snowball98%
Portuguese Snowball98%
Dutch Snowball98%
Urdu trigrams98%
Vietnamese tone folding98%
Thai segmenter98%
Chinese segmenter98%
Japanese segmenter98%
Polish Snowball96%
Turkish Snowball96%
Arabic Snowball96%
Korean bigrams96%
Bangla trigrams94%
Hindi Snowball84%
Portrait of Aron Homberg

Need search tuned to your data?

Aron Homberg, freelance and independent AI researcher: search and retrieval, embedding models and on-device AI, offers consulting services worldwide.

Use it

Add it to your own app

Write the index yourself, or hand one prompt to your coding agent.

Package

Install npm v0.4.0 (opens in a new tab) with bun add defuss-search defuss-lexicalsearch defuss-vectorsearch. Then, in Node.js, Bun or the browser:

search.js
import { apiSearchPreset } from 'defuss-lexicalsearch';import { searchPreset } from 'defuss-search';import { createHybridIndex } from 'defuss-search/indexing';const search = createHybridIndex({  documents: [    { id: 'push', fields: { symbol: 'Array.prototype.push()', description: 'Add elements to the end of an array' } },    { id: 'pop', fields: { symbol: 'Array.prototype.pop()', description: 'Remove the last element of an array' } },  ],  vectors: [[1, 0], [0, 1]],  defaults: searchPreset('efficient'),  lexical: apiSearchPreset,  lexicalSearch: { exact: { fields: ['symbol'] } },});const hits = await search.search({ text: 'Array.prototype.push()', vector: [0, 1] }, {  strategy: 'fusion', exact: 'promote',});console.log(hits[0].id); // "push": the exact symbol match ranks first

The API reference covers real embedding models, ColBERT, adapters for existing indexes, filters, pagination and cancellation.


Vibe coding

Paste this prompt into your coding agent. It links the repository, so the agent works from the current README.

Integrate defuss-search into this app: https://github.com/kyr0/defuss-search
Install defuss-search with its peers defuss-lexicalsearch and defuss-vectorsearch, and read the README first. Build the index with createHybridIndex({ documents, vectors, defaults: searchPreset('efficient') }) from defuss-search/indexing, or connect existing indexes with createLexicalRetriever, createVectorRetriever and new HybridSearch({ lexical, vector }).
Embed documents and queries with the same model; defuss-vectorsearch's Winzling runs in the browser. Pass exact: 'promote' so a query that equals a symbol or ID field ranks that document first.
For neural reranking, wrap a ColBERT model with createColbertAdapter from defuss-search/colbert and use searchPreset('reranked'), which skips the model when both sources agree.
send

Exact and semantic search anywhere, for free

Lexical precision, vector recall and an optional ColBERT reranker run wherever JavaScript runs: no search server, no hosted index, no per-query bill.

MIT licensed · benchmark data credited below

Benchmark questions