UNPKG

@claude-flow/embeddings

Version:

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

74 lines • 3.07 kB
/** * ADR-121 Phase 17 — Hybrid sparse+dense retrieval. * * Composes BM25 (sparse lexical) + dense vector search via RRF * (Phase 11) into a single fused ranking. The standard production * pattern for catching documents with rare/technical terms that * dense embeddings underweight while preserving the semantic * matching that BM25 misses. * * The two signals are not score-comparable directly (BM25 is * unbounded positive, cosine ∈ [-1, 1]) — RRF operates on ranks, * which makes it the right fusion primitive here. * * Pipeline: * 1. denseHits = denseTopK(corpus, queryVec, fetchK) * 2. sparseHits = bm25TopK(queryText, bm25Index, fetchK) * 3. fused = RRF([denseHits, sparseHits], { k, kRrf, listWeights }) * * Caller provides: * - the dense search function (any backing: AnnRouter, in-memory, * DiskANN snapshot) * - the BM25 index (built once per corpus via buildBm25Index) * * Composable with all earlier primitives: HyDE produces the query * vector, MMR-rerank the fused output, etc. */ import { type RrfFusedHit } from './rrf.js'; import { type Bm25Index } from './bm25.js'; export interface HybridRetrievalOptions { /** Final number of fused results to return. */ readonly k: number; /** * Top-N to pull from each sub-retriever (BM25 + dense) before RRF. * Default 3*k. Larger = wider input pool, more compute. */ readonly fetchK?: number; /** RRF smoothing constant (Cormack-Clarke-Büttcher default 60). */ readonly kRrf?: number; /** * Per-list weights for RRF, in [dense_weight, sparse_weight] order. * Default [1, 1]. Use to bias toward one signal — e.g. [1.5, 1.0] * to prefer dense semantic matches in general while letting BM25 * still contribute on rare-term docs. */ readonly listWeights?: readonly [number, number]; } export interface HybridRetrievalDiagnostics { readonly denseHitCount: number; readonly sparseHitCount: number; /** IDs that appeared in BOTH lists (overlap signal). */ readonly overlapIds: ReadonlyArray<string>; /** Mean BM25 score across the sparse hits, for cost attribution. */ readonly meanSparseScore: number; } export interface HybridRetrievalResult { readonly hits: ReadonlyArray<RrfFusedHit>; readonly diagnostics: HybridRetrievalDiagnostics; } /** * Run hybrid sparse+dense retrieval. Returns RRF-fused hits plus * diagnostics for observability. * * The dense search is async (matches real backings); the BM25 search * is sync (pure in-memory). Caller passes the dense search function * so this module stays decoupled from any specific ANN backing. */ export declare function hybridRetrieval(queryText: string, queryVector: Float32Array | number[], bm25Index: Bm25Index, denseSearch: (queryVec: Float32Array | number[], k: number) => Promise<ReadonlyArray<{ id: string; score?: number; }>> | ReadonlyArray<{ id: string; score?: number; }>, options: HybridRetrievalOptions): Promise<HybridRetrievalResult>; //# sourceMappingURL=hybrid-retrieval.d.ts.map