@claude-flow/embeddings
Version:
V3 Embedding Service - OpenAI, Transformers.js, Agentic-Flow (ONNX), Mock providers with hyperbolic embeddings, normalization, and chunking
137 lines • 5.49 kB
TypeScript
/**
* Embedding quantization — ADR-121 Phase 3.
*
* Two opt-in quantization paths:
*
* 1. **int8** — per-vector linear quantization. 4× memory
* reduction (float32 → int8). Works with streaming inserts
* because each vector encodes independently. Used by
* `SearchableEmbeddingCache` when `quantize: 'int8'` is set.
*
* 2. **RaBitQ** — 1-bit-per-dimension quantization via
* `@ruvector/rabitq-wasm`. 32× memory reduction. Build-once
* batch API (not streaming) — exposed here as the
* `RabitqSnapshot` class for callers that want to compress
* a fixed corpus and query it. Recall recovered via an
* exact-L2² rerank pool (default 20× the requested k).
*
* Each path is opt-in + has a measurable acceptance gate:
* int8 → ≥0.98 cosine-similarity recall after dequantize
* RaBitQ → ≥0.95 recall after rerank, 32× memory reduction
*
* For int8 the recall acceptance is measured per-vector
* (cosine(original, dequant(quant(original)))). For RaBitQ
* recall is measured against the top-k from a brute-force baseline.
*/
import type { CacheSearchHit } from './searchable-embedding-cache.js';
/**
* Encoded int8 vector: the int8 buffer + the scale factor needed to
* dequantize. Scale is per-vector (each vector's max-abs sets its own
* scale) — that's the standard approach for embedding quantization
* and preserves recall best for unit-normalized vectors.
*/
export interface Int8EncodedVector {
/** Int8 buffer, same length as the source float32 vector. */
readonly bytes: Int8Array;
/** Scale factor: float32 ≈ int8 * scale. */
readonly scale: number;
}
/**
* Encode a float32 vector as int8. Returns the byte buffer + a per-vector
* scale that maps int8 back to ~float32 on decode.
*
* Algorithm: find max-abs value, scale so max-abs maps to 127, round
* to nearest int8. This is min-max scaling clamped to [-127, 127] and
* is the cheapest path that preserves direction (which is what cosine
* similarity cares about for unit-normalized embeddings).
*/
export declare function int8Encode(vec: Float32Array): Int8EncodedVector;
/**
* Decode an int8-encoded vector back to float32. Round-trips with
* recall ≥0.98 on unit-normalized vectors (the ADR-121 Phase 3 gate).
*/
export declare function int8Decode(encoded: Int8EncodedVector): Float32Array;
/**
* Measure round-trip recall for int8 encoding on a corpus.
* Recall here = mean cosine similarity between `vec` and
* `int8Decode(int8Encode(vec))` across all corpus vectors.
*
* Used by the CI benchmark to assert the ADR-121 Phase 3 acceptance
* gate (≥0.98 recall for int8).
*/
export declare function measureInt8RoundTripRecall(corpus: Float32Array[]): {
meanRecall: number;
minRecall: number;
maxRecall: number;
count: number;
};
export interface RabitqSnapshotOptions {
/** Embedding dimension. Required. */
readonly dimension: number;
/** Random rotation seed (default 0n) — same seed = bit-identical codes. */
readonly seed?: bigint;
/** Multiplier on `k` for the exact-L2² rerank pool (default 20). */
readonly rerankFactor?: number;
}
/**
* Snapshot result — what callers get back from `search`. `id` is the
* caller-supplied key (string), preserved through the build/search
* round-trip via the snapshot's internal id-map.
*/
export interface RabitqSnapshotHit {
readonly id: string;
/** Approximate L2² distance after RaBitQ rerank. */
readonly distance: number;
/** Original vector (preserved by the snapshot for downstream use). */
readonly vector: Float32Array;
}
/**
* Batch RaBitQ snapshot — compress a corpus of vectors once, then
* query against the 1-bit index with exact-L2² rerank for recall.
*
* Lifecycle:
* const snap = await RabitqSnapshot.build(entries, { dimension: 384 });
* const hits = await snap.search(queryVec, 10);
* snap.dispose();
*
* Memory profile:
* raw = N × dim × 4 bytes (float32)
* rabitq = N × dim / 8 bytes (1 bit per dim) → 32× smaller
* (Plus a per-snapshot id-map for string → numeric position; small.)
*
* If `@ruvector/rabitq-wasm` isn't installed, `build` throws a
* clear named error so callers know which optional peer to install.
*/
export declare class RabitqSnapshot {
private readonly index;
private readonly idMap;
private readonly vectors;
private disposed;
private constructor();
static build(entries: Array<{
id: string;
vector: Float32Array;
}>, options: RabitqSnapshotOptions): Promise<RabitqSnapshot>;
/** Query the snapshot for the `k` nearest neighbors of `query`. */
search(query: Float32Array, k: number): Promise<RabitqSnapshotHit[]>;
/** Number of vectors in the snapshot. */
get size(): number;
/**
* Release the WASM index. Idempotent. Memory held by the JS-side
* id-map and vectors is GC'd normally.
*/
dispose(): void;
}
/**
* Measure the *theoretical* memory reduction for a corpus when
* encoded with RaBitQ vs raw float32. Returns the byte counts so
* callers can publish the metric to the tracker. Doesn't actually
* build the index (cheap to compute, deterministic).
*/
export declare function rabitqMemoryReduction(corpusSize: number, dimension: number): {
rawBytes: number;
rabitqBytes: number;
reductionFactor: number;
};
export type { CacheSearchHit };
//# sourceMappingURL=quantization.d.ts.map