UNPKG

@claude-flow/embeddings

Version:

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

146 lines • 6.17 kB
/** * SearchableEmbeddingCache — ADR-121 Phase 2. * * The legacy `PersistentEmbeddingCache` and the in-process `LRUCache` * only support exact-key lookup. That's fine for "I've seen this * exact text before" but useless for "I haven't computed this exact * text but I have something within ε" — which is the whole point of * a vector cache for embedding-heavy workloads. * * This module adds an HNSW-backed cache implementation that: * - Implements the same key-value contract as the legacy cache * (back-compat: existing callers don't change) * - Adds `search(query, k)` and `searchByText(text, k, embed)` for * ANN retrieval over the cached vectors * - Lazy-loads `@ruvector/core` as an optional peer dep; falls back * to in-memory exact-only when the dep isn't installed * * Phase 2 acceptance: * - ≥0.95 near-duplicate recall (where exact key match would be 0) * - ≥10k inserts/sec on the standard test machine * - Legacy PersistentEmbeddingCache unchanged */ import type { EmbeddingResult } from './types.js'; /** * Search result row. * * `score` is a **similarity** score: higher = more similar, identical * vectors → 1.0. The cache normalizes the raw distance metric returned * by `@ruvector/core` (which returns cosine *distance* and Euclidean * *distance*) into the similarity convention used elsewhere in this * package (e.g. `cosineSimilarity()`). * * Conversion table: * cosine : similarity = 1 - distance * dot-product: similarity = -distance (already in dot-product space) * euclidean : similarity = 1 / (1 + distance) (monotonic, bounded [0,1]) * manhattan : similarity = 1 / (1 + distance) */ export interface CacheSearchHit { /** Stable opaque id assigned to the cached entry. */ readonly id: string; /** The cached embedding vector. */ readonly vector: Float32Array; /** The original text the vector embeds, when available. */ readonly text?: string; /** Similarity score in [0, 1] (higher = more similar). */ readonly score: number; } export interface SearchableCacheOptions { /** Embedding dimension. Required — must match the producer service. */ readonly dimension: number; /** Distance metric (default 'cosine'). */ readonly metric?: 'cosine' | 'euclidean' | 'dot-product' | 'manhattan'; /** Max in-memory entries (default 10,000). */ readonly maxSize?: number; /** * Quantization mode for cached vectors (ADR-121 Phase 3, alpha.21): * 'none' (default) — full float32 in memory * 'int8' — per-vector int8 + scale, 4× memory reduction, * recall ≥0.98 on unit-normalized vectors * RaBitQ (1-bit, 32× reduction) is a separate batch API; see * `RabitqSnapshot` for that path — it doesn't fit the streaming * insert/get/delete contract this cache exposes. */ readonly quantize?: 'none' | 'int8'; } export declare class SearchableEmbeddingCache { private backing; private degraded; private readonly options; /** Map from cache key (text or content hash) to the assigned vector id. */ private readonly keyToId; /** Reverse map from vector id back to the original text key. */ private readonly idToKey; /** * Per-id int8 store. Only populated when `quantize === 'int8'`. The * cache continues to hand back float32 vectors to callers (dequantized * on demand) — quantization is a memory-layout concern transparent * to the IEmbeddingService contract. */ private readonly int8Store; private nextId; private hits; private misses; constructor(options: SearchableCacheOptions); /** Lazy-initialize the HNSW backing. Idempotent. */ initialize(): Promise<void>; /** Cache the embedding under the given text key. */ set(key: string, vector: Float32Array, opts?: { id?: string; }): Promise<void>; /** Exact-key lookup. Returns the cached vector or undefined. */ get(key: string): Promise<Float32Array | undefined>; /** Whether the exact key is cached. */ has(key: string): Promise<boolean>; /** Remove a cached entry by key. */ delete(key: string): Promise<boolean>; /** * ANN search over the cached embeddings. Returns top-k neighbors * by similarity, sorted descending by score (higher = more similar). */ search(query: Float32Array, k?: number): Promise<CacheSearchHit[]>; /** * Normalize whatever the backing returns (Float32Array, number[], * or an object-as-array from a NAPI bridge) into a Float32Array. */ private toFloat32Array; /** * Embed a text query (via the supplied embedder), then ANN search * for nearest cached neighbors. Convenience helper so callers * don't have to wire embed → search manually. */ searchByText(text: string, k: number, embed: (text: string) => Promise<EmbeddingResult>): Promise<CacheSearchHit[]>; /** * Snapshot of cache stats — size, hit rate, backing status. * * Size is read from the local id-map (synchronous) instead of the * backing's `len()` because the real `@ruvector/core` returns a * Promise for that. The id-map stays in lockstep with the backing * through `set`/`delete`/`clear`/`maybeEvict`, so this is exact. */ stats(): { size: number; hits: number; misses: number; hitRate: number; degraded: boolean; metric: string; dimension: number; quantize: 'none' | 'int8'; estimatedBytes: number; }; /** Clear all cached entries. */ clear(): Promise<void>; /** * FIFO eviction when over maxSize. Async because the backing's * `delete` may be async (the real NAPI binding returns a Promise). */ private maybeEvict; } /** * Factory — keeps the constructor a one-liner and matches the * createXxxService convention used elsewhere in the package. */ export declare function createSearchableEmbeddingCache(options: SearchableCacheOptions): SearchableEmbeddingCache; //# sourceMappingURL=searchable-embedding-cache.d.ts.map