UNPKG

@claude-flow/embeddings

Version:

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

142 lines • 5.79 kB
/** * ADR-121 Phase 8 — `AnnRouter` composition layer. * * The package now ships three ANN backings, each with a different * sweet spot: * * `SearchableEmbeddingCache` (HNSW + `@ruvector/core`) * - in-memory, streaming insert + delete, ANN search * - sweet spot: <100k vectors, frequently mutated, low-latency * * `RabitqSnapshot` (1-bit quantization + `@ruvector/rabitq-wasm`) * - in-memory, batch-build only, 32× memory reduction with * exact-L2² rerank * - sweet spot: 100k-1M vectors, mostly read, memory-tight * * `DiskannSnapshot` (DiskANN/Vamana + `@ruvector/diskann`) * - on-disk via `storagePath`, batch-build (with streaming insert * before build), survives process restarts * - sweet spot: >1M vectors, persistent, agent-fleet-scale * * Choosing between them requires understanding the workload — * something a downstream caller often doesn't want to think about. * `AnnRouter` takes a workload descriptor (`{ corpusSize, persistent, * mutable }`) and routes to the right backing automatically, behind a * single unified `search(query, k)` interface. * * Routing rules (documented + enumerable, see `decideBacking()`): * * persistent=true → DiskANN (only on-disk option) * corpusSize ≥ 1_000_000 → DiskANN (memory pressure) * corpusSize ≥ 100_000 + !mutable → RaBitQ (batch, compressed) * default (small, mutable, in-RAM)→ HNSW cache * * When the preferred backing's peer dep isn't installed, the router * falls back to the next-best available one and reports `degraded: * true` + a `degradationReason`. Operators always get *something* * that works (even if it's the linear-scan fallback inside the HNSW * cache) rather than a hard failure. */ export type AnnBacking = 'hnsw' | 'rabitq' | 'diskann'; export interface AnnRouterWorkload { /** Approximate corpus size (vectors). Used for the routing decision. */ readonly corpusSize: number; /** Embedding dimension. Required. */ readonly dimension: number; /** * Will the corpus need to survive process restarts? When true, * forces DiskANN regardless of size. */ readonly persistent?: boolean; /** * Will the corpus be mutated after the initial batch (streaming * inserts, deletes)? When true, prefers HNSW over RaBitQ/DiskANN * (those are batch-build). */ readonly mutable?: boolean; /** * On-disk storage path. Required when `persistent: true`. The * snapshot lives at this path and can be reopened via * `AnnRouter.load(workload)`. */ readonly storagePath?: string; } export interface AnnRouterHit { readonly id: string; /** Similarity-style score in [0, 1] for HNSW, raw distance for the snapshot backings. Lower-better unless via HNSW. */ readonly score: number; /** Original vector when the backing returns it (HNSW does, snapshot backings don't always). */ readonly vector?: Float32Array; } export interface AnnRouterDecision { /** Backing selected by `decideBacking`. */ readonly backing: AnnBacking; /** Free-form rationale — useful for logs + the routing smoke. */ readonly reason: string; /** Was a fallback used because the preferred backing wasn't available? */ readonly degraded: boolean; /** When `degraded`, why the preferred backing was unavailable. */ readonly degradationReason?: string; } /** * Pure function exposing the routing decision. Doesn't actually * instantiate anything — useful for previewing what `AnnRouter.create` * would do, and for tests that assert routing without booting NAPI. */ export declare function decideBacking(workload: AnnRouterWorkload): { backing: AnnBacking; reason: string; }; /** * Unified ANN-router handle. Always exposes the same `add`/`build`/ * `search` surface regardless of which backing is live. Callers can * inspect `decision` to see what was picked + why. */ export declare class AnnRouter { readonly decision: AnnRouterDecision; private readonly workload; private hnsw; private rabitq; private diskann; private pending; private constructor(); /** * Pick a backing, instantiate it, return the handle. The router * never throws on missing peer deps — it falls back to a less- * preferred backing and reports `degraded: true`. */ static create(workload: AnnRouterWorkload): Promise<AnnRouter>; /** * Insert vectors. For HNSW this is immediate. For RaBitQ this * buffers until `build()`. For DiskANN this calls `add()` on the * underlying snapshot (also requires `build()` before search). */ add(entries: ReadonlyArray<{ id: string; vector: Float32Array; }>): Promise<void>; /** * Build the index. No-op for HNSW (streaming); required for * RaBitQ + DiskANN before the first `search`. */ build(): Promise<void>; /** * Search the active backing. Always returns `AnnRouterHit[]` sorted * such that the more-relevant result comes first — HNSW returns * similarity-descending; RaBitQ + DiskANN return distance-ascending * (the lower the score, the better the match). Consumers reading * `score` should interpret it relative to `decision.backing`. */ search(query: Float32Array, k: number): Promise<AnnRouterHit[]>; /** * Current vector count. Differs slightly by backing (HNSW returns * cache size; RaBitQ returns batch size; DiskANN includes tombstones). */ count(): number; /** * Persist the snapshot. Only meaningful for DiskANN (the other * backings are in-memory). No-op + returns false otherwise. */ save(): boolean; } //# sourceMappingURL=ann-router.d.ts.map