UNPKG

@claude-flow/embeddings

Version:

V3 Embedding Service - OpenAI, Transformers.js, Agentic-Flow (ONNX), Mock providers with hyperbolic embeddings, normalization, and chunking

114 lines • 4.53 kB
/** * ADR-121 Phase 17 — Okapi BM25 (sparse lexical retrieval). * * Dense vector retrieval (cosine over learned embeddings) is what * Phases 9-16 ship. It's powerful but has a known failure mode: * rare or technical terms that the embedding model never saw at * training time get averaged out in the dense vector, so docs that * literally contain "CVE-2024-12345" or "sha256:abc…" rank poorly * even when they're the exact match. * * The standard production fix is **hybrid retrieval**: combine a * sparse lexical signal (BM25 — what classical search engines use) * with the dense signal (cosine), then RRF-fuse the two ranked * lists. The sparse signal catches the rare-term docs the dense * signal underweights; the dense signal catches the semantic-only * matches the sparse signal misses. * * This module ships pure-function BM25 (Okapi variant — the modern * production default since Robertson-Walker 1994) with no external * deps. Composes with the Phase 11 RRF primitive for hybrid retrieval. * * score(q, d) = Σ_{t in q} IDF(t) · (tf(t,d) · (k1 + 1)) / (tf(t,d) + k1·(1 - b + b·|d|/avgdl)) * * IDF(t) = ln((N - df(t) + 0.5) / (df(t) + 0.5) + 1) * * Parameters: * k1 ∈ [1.2, 2.0] — term frequency saturation (default 1.5) * b ∈ [0.0, 1.0] — document-length normalization (default 0.75) */ export interface Bm25Options { /** Term frequency saturation. Default 1.5 (Okapi standard). */ readonly k1?: number; /** Length normalization. Default 0.75. */ readonly b?: number; } export interface Bm25Document { readonly id: string; readonly text: string; } export interface Bm25Index { readonly docs: ReadonlyArray<{ id: string; tokens: string[]; length: number; }>; /** Document frequency: token → number of docs containing it. */ readonly df: ReadonlyMap<string, number>; /** Number of documents. */ readonly N: number; /** Average document length. */ readonly avgdl: number; readonly k1: number; readonly b: number; } export interface Bm25Hit { readonly id: string; readonly score: number; } /** * Default tokenizer: lowercase, split on non-alphanumeric, drop * empty strings, drop tokens of length < 2 (stopword-ish noise). * * Production callers can supply their own tokenizer (e.g. stemmer, * stop-word filter, n-grams) by tokenizing themselves and passing * the pre-tokenized form via `buildBm25IndexFromTokens`. */ export declare function tokenize(text: string): string[]; /** * Build a BM25 index over a corpus. O(N · avg-doc-length). * * The index is immutable — to add docs, build a new index. For * streaming corpora the right move is to maintain the (df, N, avgdl, * docs) structures explicitly and rebuild incrementally; this * module keeps it simple. */ export declare function buildBm25Index(corpus: ReadonlyArray<Bm25Document>, options?: Bm25Options): Bm25Index; /** * Score a single document against a query under the index. Used * by bm25TopK; exported for callers who want to score a specific * (query, doc) pair. */ export declare function bm25Score(queryTokens: ReadonlyArray<string>, doc: { tokens: string[]; length: number; }, index: Bm25Index): number; /** * Top-k BM25 search. Returns docs with score > 0 (docs that share * at least one token with the query), sorted descending. Ties * resolve by id ascending for determinism. * * Time complexity: O(N · |q|) where N = doc count, |q| = unique * query tokens. */ export declare function bm25TopK(query: string, index: Bm25Index, k: number): Bm25Hit[]; /** * Compute the inverse document frequency for a single token. Returns * 0 for tokens not in the corpus. Useful for the adaptive router's * rare-token-density signal. */ export declare function idfOf(token: string, index: Bm25Index): number; /** * Rare-token density of a query relative to a BM25 index. Returns * the **mean IDF** of unique tokens in the query that appear in the * corpus at least once (tokens not in the corpus would have IDF 0 * and would dilute the signal — they don't represent "rare * technical terms our retrieval can act on", they represent * "tokens our corpus doesn't have"). * * Used by the adaptive router as the hybrid-retrieval signal: high * mean IDF means the query has rare/technical terms that dense * cosine retrieval tends to underweight. */ export declare function queryMeanIdf(query: string, index: Bm25Index): number; //# sourceMappingURL=bm25.d.ts.map