verso-db 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +13 -7
  3. package/dist/BinaryHeap.d.ts +11 -1
  4. package/dist/BinaryHeap.d.ts.map +1 -1
  5. package/dist/BinaryHeap.js +138 -0
  6. package/dist/BinaryHeap.js.map +1 -0
  7. package/dist/Collection.d.ts +30 -4
  8. package/dist/Collection.d.ts.map +1 -1
  9. package/dist/Collection.js +1186 -0
  10. package/dist/Collection.js.map +1 -0
  11. package/dist/HNSWIndex.d.ts +59 -0
  12. package/dist/HNSWIndex.d.ts.map +1 -1
  13. package/dist/HNSWIndex.js +2818 -0
  14. package/dist/HNSWIndex.js.map +1 -0
  15. package/dist/MaxBinaryHeap.d.ts +2 -64
  16. package/dist/MaxBinaryHeap.d.ts.map +1 -1
  17. package/dist/MaxBinaryHeap.js +5 -0
  18. package/dist/MaxBinaryHeap.js.map +1 -0
  19. package/dist/SearchWorker.d.ts +57 -4
  20. package/dist/SearchWorker.d.ts.map +1 -1
  21. package/dist/SearchWorker.js +573 -0
  22. package/dist/SearchWorker.js.map +1 -0
  23. package/dist/VectorDB.d.ts.map +1 -1
  24. package/dist/VectorDB.js +246 -0
  25. package/dist/VectorDB.js.map +1 -0
  26. package/dist/WorkerPool.d.ts +32 -2
  27. package/dist/WorkerPool.d.ts.map +1 -1
  28. package/dist/WorkerPool.js +266 -0
  29. package/dist/WorkerPool.js.map +1 -0
  30. package/dist/backends/JsDistanceBackend.d.ts.map +1 -1
  31. package/dist/backends/JsDistanceBackend.js +163 -0
  32. package/dist/backends/JsDistanceBackend.js.map +1 -0
  33. package/dist/encoding/DeltaEncoder.d.ts +2 -2
  34. package/dist/encoding/DeltaEncoder.d.ts.map +1 -1
  35. package/dist/encoding/DeltaEncoder.js +199 -0
  36. package/dist/encoding/DeltaEncoder.js.map +1 -0
  37. package/dist/errors.js +97 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/index.d.ts +3 -3
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +61 -42
  42. package/dist/index.js.map +1 -9
  43. package/dist/presets.js +205 -0
  44. package/dist/presets.js.map +1 -0
  45. package/dist/quantization/ScalarQuantizer.d.ts +0 -34
  46. package/dist/quantization/ScalarQuantizer.d.ts.map +1 -1
  47. package/dist/quantization/ScalarQuantizer.js +346 -0
  48. package/dist/quantization/ScalarQuantizer.js.map +1 -0
  49. package/dist/storage/BatchWriter.js +351 -0
  50. package/dist/storage/BatchWriter.js.map +1 -0
  51. package/dist/storage/BunStorageBackend.d.ts +7 -3
  52. package/dist/storage/BunStorageBackend.d.ts.map +1 -1
  53. package/dist/storage/BunStorageBackend.js +182 -0
  54. package/dist/storage/BunStorageBackend.js.map +1 -0
  55. package/dist/storage/MemoryBackend.js +109 -0
  56. package/dist/storage/MemoryBackend.js.map +1 -0
  57. package/dist/storage/OPFSBackend.d.ts.map +1 -1
  58. package/dist/storage/OPFSBackend.js +325 -0
  59. package/dist/storage/OPFSBackend.js.map +1 -0
  60. package/dist/storage/StorageBackend.js +12 -0
  61. package/dist/storage/StorageBackend.js.map +1 -0
  62. package/dist/storage/WriteAheadLog.js +321 -0
  63. package/dist/storage/WriteAheadLog.js.map +1 -0
  64. package/dist/storage/createStorageBackend.d.ts +4 -0
  65. package/dist/storage/createStorageBackend.d.ts.map +1 -1
  66. package/dist/storage/createStorageBackend.js +119 -0
  67. package/dist/storage/createStorageBackend.js.map +1 -0
  68. package/{src/storage/index.ts → dist/storage/index.js} +7 -27
  69. package/dist/storage/index.js.map +1 -0
  70. package/dist/storage/nodeFsRuntime.d.ts +14 -0
  71. package/dist/storage/nodeFsRuntime.d.ts.map +1 -0
  72. package/dist/storage/nodeFsRuntime.js +105 -0
  73. package/dist/storage/nodeFsRuntime.js.map +1 -0
  74. package/package.json +9 -7
  75. package/src/BinaryHeap.ts +0 -136
  76. package/src/Collection.ts +0 -1262
  77. package/src/HNSWIndex.ts +0 -2894
  78. package/src/MaxBinaryHeap.ts +0 -181
  79. package/src/SearchWorker.ts +0 -264
  80. package/src/VectorDB.ts +0 -319
  81. package/src/WorkerPool.ts +0 -222
  82. package/src/backends/JsDistanceBackend.ts +0 -171
  83. package/src/encoding/DeltaEncoder.ts +0 -236
  84. package/src/errors.ts +0 -110
  85. package/src/index.ts +0 -106
  86. package/src/presets.ts +0 -229
  87. package/src/quantization/ScalarQuantizer.ts +0 -487
  88. package/src/storage/BatchWriter.ts +0 -420
  89. package/src/storage/BunStorageBackend.ts +0 -199
  90. package/src/storage/MemoryBackend.ts +0 -122
  91. package/src/storage/OPFSBackend.ts +0 -348
  92. package/src/storage/StorageBackend.ts +0 -74
  93. package/src/storage/WriteAheadLog.ts +0 -379
  94. package/src/storage/createStorageBackend.ts +0 -137
@@ -1,236 +0,0 @@
1
- /**
2
- * Delta Encoding with Varint for Neighbor Lists
3
- *
4
- * Implements delta-encoded neighbor lists as used by Qdrant for ~38% storage reduction.
5
- * Neighbor IDs are sorted, then stored as deltas with variable-length encoding.
6
- *
7
- * Format:
8
- * - First ID stored as full uint32
9
- * - Subsequent IDs stored as varint deltas from previous ID
10
- *
11
- * Varint encoding (like Protocol Buffers):
12
- * - Values 0-127: 1 byte
13
- * - Values 128-16383: 2 bytes
14
- * - Values 16384-2097151: 3 bytes
15
- * - Values 2097152-268435455: 4 bytes
16
- * - Larger: 5 bytes
17
- */
18
-
19
- /**
20
- * Encode an unsigned integer as a varint
21
- * Returns the number of bytes written
22
- */
23
- export function encodeVarint(value: number, buffer: Uint8Array, offset: number): number {
24
- let v = value >>> 0; // Ensure unsigned
25
- let bytesWritten = 0;
26
-
27
- while (v >= 0x80) {
28
- buffer[offset + bytesWritten] = (v & 0x7f) | 0x80;
29
- v >>>= 7;
30
- bytesWritten++;
31
- }
32
- buffer[offset + bytesWritten] = v;
33
- return bytesWritten + 1;
34
- }
35
-
36
- /**
37
- * Decode a varint from buffer
38
- * Returns [value, bytesRead]
39
- */
40
- export function decodeVarint(buffer: Uint8Array, offset: number): [number, number] {
41
- let result = 0;
42
- let shift = 0;
43
- let bytesRead = 0;
44
-
45
- while (offset + bytesRead < buffer.length) {
46
- const byte = buffer[offset + bytesRead];
47
- if (shift < 28) {
48
- result |= (byte & 0x7f) << shift;
49
- } else {
50
- // At shift >= 28, use multiplication to avoid sign-bit corruption from << on int32
51
- result = (result + (byte & 0x7f) * (2 ** shift)) >>> 0;
52
- }
53
- bytesRead++;
54
-
55
- if ((byte & 0x80) === 0) {
56
- return [result >>> 0, bytesRead];
57
- }
58
-
59
- shift += 7;
60
- if (shift > 35) {
61
- throw new Error('Varint too long');
62
- }
63
- }
64
-
65
- throw new Error('Unexpected end of buffer');
66
- }
67
-
68
- /**
69
- * Calculate the number of bytes needed to encode a varint
70
- */
71
- export function varintSize(value: number): number {
72
- let v = value >>> 0;
73
- let size = 1;
74
- while (v >= 0x80) {
75
- v >>>= 7;
76
- size++;
77
- }
78
- return size;
79
- }
80
-
81
- /**
82
- * Delta-encode a sorted array of neighbor IDs
83
- * Returns the encoded buffer
84
- */
85
- export function deltaEncodeNeighbors(neighbors: number[]): Uint8Array {
86
- if (neighbors.length === 0) {
87
- return new Uint8Array(0);
88
- }
89
-
90
- for (let i = 0; i < neighbors.length; i++) {
91
- const id = neighbors[i];
92
- if (!Number.isInteger(id) || id < 0 || id > 0xFFFFFFFF) {
93
- throw new Error(`Invalid neighbor ID at index ${i}: ${id}`);
94
- }
95
- }
96
-
97
- // Sort neighbors for optimal delta encoding
98
- const sorted = neighbors.slice().sort((a, b) => a - b);
99
-
100
- // Calculate required buffer size
101
- let size = 4; // First ID as uint32
102
- let prev = sorted[0];
103
- for (let i = 1; i < sorted.length; i++) {
104
- const delta = sorted[i] - prev;
105
- size += varintSize(delta);
106
- prev = sorted[i];
107
- }
108
-
109
- // Encode
110
- const buffer = new Uint8Array(size);
111
- const view = new DataView(buffer.buffer);
112
-
113
- // First ID as full uint32 (little-endian)
114
- view.setUint32(0, sorted[0], true);
115
- let offset = 4;
116
-
117
- // Remaining as deltas
118
- prev = sorted[0];
119
- for (let i = 1; i < sorted.length; i++) {
120
- const delta = sorted[i] - prev;
121
- offset += encodeVarint(delta, buffer, offset);
122
- prev = sorted[i];
123
- }
124
-
125
- return buffer;
126
- }
127
-
128
- /**
129
- * Decode a delta-encoded neighbor list
130
- * Returns the original neighbor IDs (sorted)
131
- */
132
- export function deltaDecodeNeighbors(buffer: Uint8Array, count: number): number[] {
133
- if (count === 0 || buffer.length === 0) {
134
- return [];
135
- }
136
-
137
- const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
138
- const neighbors = new Array<number>(count);
139
-
140
- // First ID as full uint32
141
- neighbors[0] = view.getUint32(0, true);
142
- let offset = 4;
143
-
144
- // Remaining as deltas
145
- for (let i = 1; i < count; i++) {
146
- const [delta, bytesRead] = decodeVarint(buffer, offset);
147
- neighbors[i] = neighbors[i - 1] + delta;
148
- offset += bytesRead;
149
- }
150
-
151
- return neighbors;
152
- }
153
-
154
- /**
155
- * Calculate the encoded size for a neighbor list without actually encoding
156
- * Useful for calculating total buffer size before serialization
157
- */
158
- export function deltaEncodedSize(neighbors: number[]): number {
159
- if (neighbors.length === 0) {
160
- return 0;
161
- }
162
-
163
- for (let i = 0; i < neighbors.length; i++) {
164
- const id = neighbors[i];
165
- if (!Number.isInteger(id) || id < 0 || id > 0xFFFFFFFF) {
166
- throw new Error(`Invalid neighbor ID at index ${i}: ${id}`);
167
- }
168
- }
169
-
170
- const sorted = neighbors.slice().sort((a, b) => a - b);
171
-
172
- let size = 4; // First ID as uint32
173
- let prev = sorted[0];
174
- for (let i = 1; i < sorted.length; i++) {
175
- const delta = sorted[i] - prev;
176
- size += varintSize(delta);
177
- prev = sorted[i];
178
- }
179
-
180
- return size;
181
- }
182
-
183
- /**
184
- * Batch encode multiple neighbor lists efficiently
185
- * Returns a single buffer with all encoded lists concatenated
186
- * Also returns offsets for each list
187
- */
188
- export function deltaEncodeBatch(neighborLists: number[][]): {
189
- buffer: Uint8Array;
190
- offsets: number[];
191
- sizes: number[];
192
- } {
193
- // Calculate total size and individual sizes
194
- const sizes = neighborLists.map(list => deltaEncodedSize(list));
195
- const totalSize = sizes.reduce((a, b) => a + b, 0);
196
-
197
- // Allocate single buffer
198
- const buffer = new Uint8Array(totalSize);
199
- const offsets: number[] = [];
200
-
201
- let currentOffset = 0;
202
- for (let i = 0; i < neighborLists.length; i++) {
203
- offsets.push(currentOffset);
204
-
205
- if (neighborLists[i].length > 0) {
206
- const encoded = deltaEncodeNeighbors(neighborLists[i]);
207
- buffer.set(encoded, currentOffset);
208
- currentOffset += encoded.length;
209
- }
210
- }
211
-
212
- return { buffer, offsets, sizes };
213
- }
214
-
215
- /**
216
- * Decode a batch of neighbor lists from a single buffer
217
- */
218
- export function deltaDecodeBatch(
219
- buffer: Uint8Array,
220
- offsets: number[],
221
- sizes: number[],
222
- counts: number[]
223
- ): number[][] {
224
- const results: number[][] = [];
225
-
226
- for (let i = 0; i < offsets.length; i++) {
227
- if (counts[i] === 0 || sizes[i] === 0) {
228
- results.push([]);
229
- } else {
230
- const slice = buffer.subarray(offsets[i], offsets[i] + sizes[i]);
231
- results.push(deltaDecodeNeighbors(slice, counts[i]));
232
- }
233
- }
234
-
235
- return results;
236
- }
package/src/errors.ts DELETED
@@ -1,110 +0,0 @@
1
- /**
2
- * Base error class for all VectorDB errors
3
- */
4
- export class VectorDBError extends Error {
5
- readonly code: string;
6
-
7
- constructor(message: string, code: string) {
8
- super(message);
9
- this.name = 'VectorDBError';
10
- this.code = code;
11
- }
12
- }
13
-
14
- /**
15
- * Thrown when vector dimensions don't match expected dimensions
16
- */
17
- export class DimensionMismatchError extends VectorDBError {
18
- readonly expected: number;
19
- readonly actual: number;
20
-
21
- constructor(expected: number, actual: number, context?: string) {
22
- const message = context
23
- ? `${context}: expected dimension ${expected}, got ${actual}`
24
- : `Dimension mismatch: expected ${expected}, got ${actual}`;
25
- super(message, 'DIMENSION_MISMATCH');
26
- this.name = 'DimensionMismatchError';
27
- this.expected = expected;
28
- this.actual = actual;
29
- }
30
- }
31
-
32
- /**
33
- * Thrown when attempting to add a vector with an ID that already exists
34
- */
35
- export class DuplicateVectorError extends VectorDBError {
36
- readonly ids: string[];
37
-
38
- constructor(ids: string[]) {
39
- const message = ids.length === 1
40
- ? `Vector with ID '${ids[0]}' already exists`
41
- : `Vectors with IDs already exist: ${ids.join(', ')}`;
42
- super(message, 'DUPLICATE_VECTOR');
43
- this.name = 'DuplicateVectorError';
44
- this.ids = ids;
45
- }
46
- }
47
-
48
- /**
49
- * Thrown when a requested collection does not exist
50
- */
51
- export class CollectionNotFoundError extends VectorDBError {
52
- readonly collectionName: string;
53
-
54
- constructor(collectionName: string) {
55
- super(`Collection '${collectionName}' does not exist`, 'COLLECTION_NOT_FOUND');
56
- this.name = 'CollectionNotFoundError';
57
- this.collectionName = collectionName;
58
- }
59
- }
60
-
61
- /**
62
- * Thrown when attempting to create a collection that already exists
63
- */
64
- export class CollectionExistsError extends VectorDBError {
65
- readonly collectionName: string;
66
-
67
- constructor(collectionName: string) {
68
- super(`Collection '${collectionName}' already exists`, 'COLLECTION_EXISTS');
69
- this.name = 'CollectionExistsError';
70
- this.collectionName = collectionName;
71
- }
72
- }
73
-
74
- /**
75
- * Thrown when a storage operation fails
76
- */
77
- export class StorageError extends VectorDBError {
78
- readonly operation: string;
79
- readonly path?: string;
80
-
81
- constructor(operation: string, message: string, path?: string) {
82
- super(`Storage ${operation} failed: ${message}`, 'STORAGE_ERROR');
83
- this.name = 'StorageError';
84
- this.operation = operation;
85
- this.path = path;
86
- }
87
- }
88
-
89
- /**
90
- * Thrown when quantization operations fail
91
- */
92
- export class QuantizationError extends VectorDBError {
93
- constructor(message: string) {
94
- super(message, 'QUANTIZATION_ERROR');
95
- this.name = 'QuantizationError';
96
- }
97
- }
98
-
99
- /**
100
- * Thrown when a vector is not found in the index
101
- */
102
- export class VectorNotFoundError extends VectorDBError {
103
- readonly vectorId: string | number;
104
-
105
- constructor(vectorId: string | number) {
106
- super(`Vector '${vectorId}' not found`, 'VECTOR_NOT_FOUND');
107
- this.name = 'VectorNotFoundError';
108
- this.vectorId = vectorId;
109
- }
110
- }
package/src/index.ts DELETED
@@ -1,106 +0,0 @@
1
- /**
2
- * verso-db - High-performance vector search with HNSW indexing
3
- *
4
- * Features:
5
- * - HNSW algorithm for approximate nearest neighbor search
6
- * - Multiple distance metrics: cosine, euclidean, dot product
7
- * - Int8 scalar quantization for 4x memory reduction
8
- * - Batch query support for improved throughput
9
- * - Parameter presets for different use cases
10
- * - Multi-platform: Bun (file system) and Browser (OPFS)
11
- *
12
- * @example
13
- * ```typescript
14
- * import { VectorDB, getRecommendedPreset } from 'verso-db';
15
- *
16
- * const db = new VectorDB({ storagePath: './vectors' });
17
- * const preset = getRecommendedPreset(768);
18
- *
19
- * const collection = await db.createCollection('my-vectors', {
20
- * dimension: 768,
21
- * metric: 'cosine',
22
- * M: preset.M,
23
- * efConstruction: preset.efConstruction
24
- * });
25
- *
26
- * await collection.add({
27
- * ids: ['doc1', 'doc2'],
28
- * vectors: [new Float32Array(768), new Float32Array(768)]
29
- * });
30
- *
31
- * const results = await collection.query({
32
- * queryVector: new Float32Array(768),
33
- * k: 10,
34
- * efSearch: preset.efSearch
35
- * });
36
- * ```
37
- *
38
- * @packageDocumentation
39
- * @module verso-db
40
- */
41
-
42
- // ─── Public API ──────────────────────────────────────────────────────────────
43
-
44
- // Core
45
- export { VectorDB } from './VectorDB';
46
- export type { VectorDBConfig, CollectionConfig } from './VectorDB';
47
- export { Collection } from './Collection';
48
- export type { AddConfig, QueryConfig, QueryResult } from './Collection';
49
- export { HNSWIndex } from './HNSWIndex';
50
- export type { DistanceMetric } from './HNSWIndex';
51
-
52
- // Parameter presets
53
- export type { HNSWPreset } from './presets';
54
- export {
55
- PRESET_LOW_DIM,
56
- PRESET_MEDIUM_DIM,
57
- PRESET_HIGH_DIM,
58
- PRESET_VERY_HIGH_DIM,
59
- PRESET_SMALL_DATASET,
60
- PRESET_LARGE_DATASET,
61
- PRESET_MAX_RECALL,
62
- PRESET_LOW_LATENCY,
63
- PRESETS,
64
- getRecommendedPreset,
65
- getPreset,
66
- getRAGPreset
67
- } from './presets';
68
-
69
- // Quantization
70
- export { ScalarQuantizer, QuantizedVectorStore } from './quantization/ScalarQuantizer';
71
- export type { QuantizationParams } from './quantization/ScalarQuantizer';
72
-
73
- // Storage backends
74
- export type { StorageBackend, StorageOptions } from './storage/StorageBackend';
75
- export { BunStorageBackend } from './storage/BunStorageBackend';
76
- export { MemoryBackend } from './storage/MemoryBackend';
77
- export { OPFSBackend } from './storage/OPFSBackend';
78
- export {
79
- createStorageBackend,
80
- getRecommendedStorageType,
81
- isStorageTypeAvailable,
82
- type StorageType,
83
- type CreateStorageOptions,
84
- } from './storage/createStorageBackend';
85
-
86
- // Write-ahead log and batch writer
87
- export { WriteAheadLog, WALOperationType } from './storage/WriteAheadLog';
88
- export type { WALEntry } from './storage/WriteAheadLog';
89
- export { BatchWriter, createBatchWriter } from './storage/BatchWriter';
90
- export type { BatchWriterOptions } from './storage/BatchWriter';
91
-
92
- // Parallel query processing
93
- export { WorkerPool } from './WorkerPool';
94
- export { WorkerSearchState } from './SearchWorker';
95
-
96
- // Error classes
97
- export {
98
- VectorDBError,
99
- DimensionMismatchError,
100
- DuplicateVectorError,
101
- CollectionNotFoundError,
102
- CollectionExistsError,
103
- StorageError,
104
- QuantizationError,
105
- VectorNotFoundError,
106
- } from './errors';
package/src/presets.ts DELETED
@@ -1,229 +0,0 @@
1
- /**
2
- * HNSW Parameter Presets
3
- *
4
- * These presets are optimized based on extensive benchmarking to achieve
5
- * the target recall@10 >= 95% for different dataset sizes and dimensions.
6
- *
7
- * Key parameters:
8
- * - M: Maximum connections per node (higher = better recall, more memory)
9
- * - efConstruction: Beam width during index building (higher = better quality, slower build)
10
- * - efSearch: Beam width during search (higher = better recall, slower search)
11
- */
12
-
13
- export interface HNSWPreset {
14
- name: string;
15
- description: string;
16
- M: number;
17
- efConstruction: number;
18
- efSearch: number;
19
- expectedRecall: number;
20
- targetDimensions: string;
21
- targetDatasetSize: string;
22
- }
23
-
24
- /**
25
- * Preset for low-dimensional vectors (128D or less)
26
- * Suitable for: Image features, word2vec, GloVe embeddings
27
- */
28
- export const PRESET_LOW_DIM: Readonly<HNSWPreset> = Object.freeze({
29
- name: 'low-dim',
30
- description: 'Optimized for low-dimensional vectors (<=128D)',
31
- M: 16,
32
- efConstruction: 200,
33
- efSearch: 100,
34
- expectedRecall: 0.99,
35
- targetDimensions: '<=128',
36
- targetDatasetSize: '1K-100K',
37
- });
38
-
39
- /**
40
- * Preset for medium-dimensional vectors (256-512D)
41
- * Suitable for: Sentence embeddings, smaller transformer outputs
42
- */
43
- export const PRESET_MEDIUM_DIM: Readonly<HNSWPreset> = Object.freeze({
44
- name: 'medium-dim',
45
- description: 'Optimized for medium-dimensional vectors (129-512D)',
46
- M: 24,
47
- efConstruction: 200,
48
- efSearch: 150,
49
- expectedRecall: 0.97,
50
- targetDimensions: '129-512',
51
- targetDatasetSize: '1K-100K',
52
- });
53
-
54
- /**
55
- * Preset for high-dimensional vectors (768D+)
56
- * Suitable for: BERT, GPT embeddings, Cohere, OpenAI embeddings
57
- * This is the recommended preset for RAG applications
58
- *
59
- * Benchmarked on Cohere Wikipedia 1024D (495K vectors):
60
- * - efSearch=128: 99.2% recall, 168 QPS, 10.72ms P99
61
- */
62
- export const PRESET_HIGH_DIM: Readonly<HNSWPreset> = Object.freeze({
63
- name: 'high-dim',
64
- description: 'Optimized for high-dimensional vectors (768D+)',
65
- M: 32,
66
- efConstruction: 200,
67
- efSearch: 128,
68
- expectedRecall: 0.99,
69
- targetDimensions: '>=768',
70
- targetDatasetSize: '1K-500K',
71
- });
72
-
73
- /**
74
- * Preset for very high-dimensional vectors (1536D+)
75
- * Suitable for: OpenAI text-embedding-ada-002, text-embedding-3-large
76
- *
77
- * Scaled from PRESET_HIGH_DIM benchmarks (higher M for higher dimensions)
78
- */
79
- export const PRESET_VERY_HIGH_DIM: Readonly<HNSWPreset> = Object.freeze({
80
- name: 'very-high-dim',
81
- description: 'Optimized for very high-dimensional vectors (1536D+)',
82
- M: 48,
83
- efConstruction: 300,
84
- efSearch: 150,
85
- expectedRecall: 0.99,
86
- targetDimensions: '>=1536',
87
- targetDatasetSize: '1K-500K',
88
- });
89
-
90
- /**
91
- * Preset for small datasets (<10K vectors)
92
- * Prioritizes recall over speed since brute-force is viable
93
- */
94
- export const PRESET_SMALL_DATASET: Readonly<HNSWPreset> = Object.freeze({
95
- name: 'small-dataset',
96
- description: 'Optimized for small datasets (<10K vectors)',
97
- M: 16,
98
- efConstruction: 200,
99
- efSearch: 200,
100
- expectedRecall: 0.99,
101
- targetDimensions: 'any',
102
- targetDatasetSize: '<10K',
103
- });
104
-
105
- /**
106
- * Preset for large datasets (100K-1M vectors)
107
- * Balances recall with build time and memory
108
- *
109
- * Benchmarked on Cohere Wikipedia 1024D (495K vectors):
110
- * - efSearch=128: 99.2% recall, 168 QPS
111
- */
112
- export const PRESET_LARGE_DATASET: Readonly<HNSWPreset> = Object.freeze({
113
- name: 'large-dataset',
114
- description: 'Optimized for large datasets (100K-1M vectors)',
115
- M: 32,
116
- efConstruction: 200,
117
- efSearch: 128,
118
- expectedRecall: 0.99,
119
- targetDimensions: 'any',
120
- targetDatasetSize: '100K-1M',
121
- });
122
-
123
- /**
124
- * Preset for maximum recall (prioritizes accuracy over speed)
125
- * Use when recall is critical and latency is acceptable
126
- */
127
- export const PRESET_MAX_RECALL: Readonly<HNSWPreset> = Object.freeze({
128
- name: 'max-recall',
129
- description: 'Maximum recall configuration',
130
- M: 48,
131
- efConstruction: 500,
132
- efSearch: 400,
133
- expectedRecall: 0.99,
134
- targetDimensions: 'any',
135
- targetDatasetSize: 'any',
136
- });
137
-
138
- /**
139
- * Preset for minimum latency (prioritizes speed over recall)
140
- * Use when latency is critical and 90% recall is acceptable
141
- */
142
- export const PRESET_LOW_LATENCY: Readonly<HNSWPreset> = Object.freeze({
143
- name: 'low-latency',
144
- description: 'Minimum latency configuration (90% recall)',
145
- M: 12,
146
- efConstruction: 100,
147
- efSearch: 50,
148
- expectedRecall: 0.90,
149
- targetDimensions: 'any',
150
- targetDatasetSize: 'any',
151
- });
152
-
153
- /**
154
- * All available presets
155
- */
156
- export const PRESETS: Readonly<Record<string, Readonly<HNSWPreset>>> = Object.freeze({
157
- 'low-dim': PRESET_LOW_DIM,
158
- 'medium-dim': PRESET_MEDIUM_DIM,
159
- 'high-dim': PRESET_HIGH_DIM,
160
- 'very-high-dim': PRESET_VERY_HIGH_DIM,
161
- 'small-dataset': PRESET_SMALL_DATASET,
162
- 'large-dataset': PRESET_LARGE_DATASET,
163
- 'max-recall': PRESET_MAX_RECALL,
164
- 'low-latency': PRESET_LOW_LATENCY,
165
- });
166
-
167
- /**
168
- * Get recommended preset based on dimension and dataset size
169
- *
170
- * For high-dimensional vectors (768D+), dimension takes priority over dataset size
171
- * because recall degrades significantly without higher M values.
172
- */
173
- export function getRecommendedPreset(dimension: number, datasetSize?: number): HNSWPreset {
174
- // For high-dimensional vectors, always use dimension-based presets
175
- // (dimension matters more than dataset size for recall)
176
- // Note: Check 1536 BEFORE 768 since 1536 >= 768 would match first
177
- if (dimension >= 1536) return PRESET_VERY_HIGH_DIM;
178
- if (dimension >= 768) return PRESET_HIGH_DIM;
179
-
180
- // For lower dimensions, consider dataset size
181
- if (datasetSize !== undefined) {
182
- if (datasetSize < 10000) return PRESET_SMALL_DATASET;
183
- if (datasetSize > 100000) return PRESET_LARGE_DATASET;
184
- }
185
-
186
- // Dimension-based selection for medium dimensions
187
- if (dimension <= 128) return PRESET_LOW_DIM;
188
- if (dimension <= 512) return PRESET_MEDIUM_DIM;
189
-
190
- return PRESET_HIGH_DIM;
191
- }
192
-
193
- /**
194
- * Get preset by name
195
- */
196
- export function getPreset(name: string): HNSWPreset | undefined {
197
- return PRESETS[name];
198
- }
199
-
200
- /**
201
- * RAG-specific preset recommendation
202
- * For typical RAG applications using popular embedding models
203
- */
204
- export function getRAGPreset(embeddingModel: string): HNSWPreset {
205
- const model = embeddingModel.toLowerCase();
206
-
207
- // OpenAI models
208
- if (model.includes('ada-002') || model.includes('text-embedding-3')) {
209
- return PRESET_VERY_HIGH_DIM;
210
- }
211
-
212
- // Cohere models
213
- if (model.includes('cohere') || model.includes('embed-')) {
214
- return PRESET_HIGH_DIM;
215
- }
216
-
217
- // BERT/Sentence Transformers
218
- if (model.includes('bert') || model.includes('minilm') || model.includes('mpnet')) {
219
- return PRESET_HIGH_DIM;
220
- }
221
-
222
- // E5 models
223
- if (model.includes('e5-')) {
224
- return model.includes('large') ? PRESET_HIGH_DIM : PRESET_MEDIUM_DIM;
225
- }
226
-
227
- // Default to high-dim for unknown models (most modern embeddings are 768D+)
228
- return PRESET_HIGH_DIM;
229
- }