Skip to content

Commit 2d7014c

Browse files
committed
feat(search): add SVS-VAMANA vector index algorithm support
- Add VAMANA algorithm with compression and tuning parameters - Include comprehensive test coverage for various configurations - Fix parameter validation to handle falsy values correctly
1 parent d8e14fa commit 2d7014c

File tree

2 files changed

+180
-8
lines changed

2 files changed

+180
-8
lines changed

packages/search/lib/commands/CREATE.spec.ts

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { strict as assert } from 'node:assert';
22
import testUtils, { GLOBAL } from '../test-utils';
3-
import CREATE, { SCHEMA_FIELD_TYPE, SCHEMA_TEXT_FIELD_PHONETIC, SCHEMA_VECTOR_FIELD_ALGORITHM, REDISEARCH_LANGUAGE } from './CREATE';
3+
import CREATE, { SCHEMA_FIELD_TYPE, SCHEMA_TEXT_FIELD_PHONETIC, SCHEMA_VECTOR_FIELD_ALGORITHM, REDISEARCH_LANGUAGE, VAMANA_COMPRESSION_ALGORITHM } from './CREATE';
44
import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
55

66
describe('FT.CREATE', () => {
@@ -206,6 +206,33 @@ describe('FT.CREATE', () => {
206206
]
207207
);
208208
});
209+
210+
it('VAMANA algorithm', () => {
211+
assert.deepEqual(
212+
parseArgs(CREATE, 'index', {
213+
field: {
214+
type: SCHEMA_FIELD_TYPE.VECTOR,
215+
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
216+
TYPE: "FLOAT32",
217+
COMPRESSION: VAMANA_COMPRESSION_ALGORITHM.LVQ8,
218+
DIM: 1024,
219+
DISTANCE_METRIC: 'COSINE',
220+
CONSTRUCTION_WINDOW_SIZE: 300,
221+
GRAPH_MAX_DEGREE: 128,
222+
SEARCH_WINDOW_SIZE: 20,
223+
EPSILON: 0.02,
224+
TRAINING_THRESHOLD: 20480,
225+
REDUCE: 512,
226+
}
227+
}),
228+
[
229+
'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'SVS-VAMANA', '20', 'TYPE',
230+
'FLOAT32', 'DIM', '1024', 'DISTANCE_METRIC', 'COSINE', 'COMPRESSION', 'LVQ8',
231+
'CONSTRUCTION_WINDOW_SIZE', '300', 'GRAPH_MAX_DEGREE', '128', 'SEARCH_WINDOW_SIZE', '20',
232+
'EPSILON', '0.02', 'TRAINING_THRESHOLD', '20480', 'REDUCE', '512'
233+
]
234+
);
235+
});
209236
});
210237

211238
describe('GEOSHAPE', () => {
@@ -556,4 +583,87 @@ describe('FT.CREATE', () => {
556583
"OK"
557584
);
558585
}, GLOBAL.SERVERS.OPEN);
586+
587+
testUtils.testWithClientIfVersionWithinRange([[8, 2], 'LATEST'], 'client.ft.create vector svs-vamana', async client => {
588+
assert.equal(
589+
await client.ft.create("index_svs_vamana_min_config", {
590+
field: {
591+
type: SCHEMA_FIELD_TYPE.VECTOR,
592+
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
593+
TYPE: "FLOAT32",
594+
DIM: 768,
595+
DISTANCE_METRIC: 'L2',
596+
},
597+
}),
598+
"OK"
599+
);
600+
601+
assert.equal(
602+
await client.ft.create("index_svs_vamana_no_compression", {
603+
field: {
604+
type: SCHEMA_FIELD_TYPE.VECTOR,
605+
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
606+
TYPE: "FLOAT32",
607+
DIM: 512,
608+
DISTANCE_METRIC: 'L2',
609+
CONSTRUCTION_WINDOW_SIZE: 200,
610+
GRAPH_MAX_DEGREE: 64,
611+
SEARCH_WINDOW_SIZE: 50,
612+
EPSILON: 0.01
613+
},
614+
}),
615+
"OK"
616+
);
617+
618+
assert.equal(
619+
await client.ft.create("index_svs_vamana_compression", {
620+
field: {
621+
type: SCHEMA_FIELD_TYPE.VECTOR,
622+
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
623+
TYPE: "FLOAT32",
624+
COMPRESSION: VAMANA_COMPRESSION_ALGORITHM.LVQ8,
625+
DIM: 1024,
626+
DISTANCE_METRIC: 'COSINE',
627+
CONSTRUCTION_WINDOW_SIZE: 300,
628+
GRAPH_MAX_DEGREE: 128,
629+
SEARCH_WINDOW_SIZE: 20,
630+
EPSILON: 0.02,
631+
TRAINING_THRESHOLD: 20480,
632+
REDUCE: 512,
633+
},
634+
}),
635+
"OK"
636+
);
637+
638+
assert.equal(
639+
await client.ft.create("index_svs_vamana_float16", {
640+
field: {
641+
type: SCHEMA_FIELD_TYPE.VECTOR,
642+
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
643+
TYPE: "FLOAT16",
644+
DIM: 128,
645+
DISTANCE_METRIC: 'IP',
646+
},
647+
}),
648+
"OK"
649+
);
650+
651+
await assert.rejects(
652+
client.ft.create("index_svs_vamana_invalid_config", {
653+
field: {
654+
type: SCHEMA_FIELD_TYPE.VECTOR,
655+
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
656+
TYPE: "FLOAT32",
657+
DIM: 2,
658+
DISTANCE_METRIC: 'L2',
659+
CONSTRUCTION_WINDOW_SIZE: 200,
660+
GRAPH_MAX_DEGREE: 64,
661+
SEARCH_WINDOW_SIZE: 50,
662+
EPSILON: 0.01,
663+
// TRAINING_THRESHOLD should error without COMPRESSION
664+
TRAINING_THRESHOLD: 2048
665+
},
666+
}),
667+
)
668+
}, GLOBAL.SERVERS.OPEN);
559669
});

packages/search/lib/commands/CREATE.ts

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ interface SchemaTagField extends SchemaCommonField<typeof SCHEMA_FIELD_TYPE['TAG
5454

5555
export const SCHEMA_VECTOR_FIELD_ALGORITHM = {
5656
FLAT: 'FLAT',
57-
HNSW: 'HNSW'
57+
HNSW: 'HNSW',
58+
/**
59+
* available since 8.2
60+
*/
61+
VAMANA: 'SVS-VAMANA'
5862
} as const;
5963

6064
export type SchemaVectorFieldAlgorithm = typeof SCHEMA_VECTOR_FIELD_ALGORITHM[keyof typeof SCHEMA_VECTOR_FIELD_ALGORITHM];
@@ -79,6 +83,32 @@ interface SchemaHNSWVectorField extends SchemaVectorField {
7983
EF_RUNTIME?: number;
8084
}
8185

86+
export const VAMANA_COMPRESSION_ALGORITHM = {
87+
LVQ8: 'LVQ8',
88+
} as const;
89+
90+
export type VamanaCompressionAlgorithm =
91+
typeof VAMANA_COMPRESSION_ALGORITHM[keyof typeof VAMANA_COMPRESSION_ALGORITHM];
92+
93+
interface SchemaVAMANAVectorField extends SchemaVectorField {
94+
ALGORITHM: typeof SCHEMA_VECTOR_FIELD_ALGORITHM['VAMANA'];
95+
TYPE: 'FLOAT16' | 'FLOAT32';
96+
// VAMANA-specific parameters
97+
COMPRESSION?: VamanaCompressionAlgorithm;
98+
CONSTRUCTION_WINDOW_SIZE?: number;
99+
GRAPH_MAX_DEGREE?: number;
100+
SEARCH_WINDOW_SIZE?: number;
101+
EPSILON?: number;
102+
/**
103+
* applicable only with COMPRESSION
104+
*/
105+
TRAINING_THRESHOLD?: number;
106+
/**
107+
* applicable only with LeanVec COMPRESSION
108+
*/
109+
REDUCE?: number;
110+
}
111+
82112
export const SCHEMA_GEO_SHAPE_COORD_SYSTEM = {
83113
SPHERICAL: 'SPHERICAL',
84114
FLAT: 'FLAT'
@@ -98,6 +128,7 @@ export interface RediSearchSchema {
98128
SchemaTagField |
99129
SchemaFlatVectorField |
100130
SchemaHNSWVectorField |
131+
SchemaVAMANAVectorField |
101132
SchemaGeoShapeField |
102133
SchemaFieldType
103134
);
@@ -142,7 +173,7 @@ export function parseSchema(parser: CommandParser, schema: RediSearchSchema) {
142173
parser.push('NOSTEM');
143174
}
144175

145-
if (fieldOptions.WEIGHT) {
176+
if (fieldOptions.WEIGHT !== undefined) {
146177
parser.push('WEIGHT', fieldOptions.WEIGHT.toString());
147178
}
148179

@@ -197,31 +228,62 @@ export function parseSchema(parser: CommandParser, schema: RediSearchSchema) {
197228
'DISTANCE_METRIC', fieldOptions.DISTANCE_METRIC
198229
);
199230

200-
if (fieldOptions.INITIAL_CAP) {
231+
if (fieldOptions.INITIAL_CAP !== undefined) {
201232
args.push('INITIAL_CAP', fieldOptions.INITIAL_CAP.toString());
202233
}
203234

204235
switch (fieldOptions.ALGORITHM) {
205236
case SCHEMA_VECTOR_FIELD_ALGORITHM.FLAT:
206-
if (fieldOptions.BLOCK_SIZE) {
237+
if (fieldOptions.BLOCK_SIZE !== undefined) {
207238
args.push('BLOCK_SIZE', fieldOptions.BLOCK_SIZE.toString());
208239
}
209240

210241
break;
211242

212243
case SCHEMA_VECTOR_FIELD_ALGORITHM.HNSW:
213-
if (fieldOptions.M) {
244+
if (fieldOptions.M !== undefined) {
214245
args.push('M', fieldOptions.M.toString());
215246
}
216247

217-
if (fieldOptions.EF_CONSTRUCTION) {
248+
if (fieldOptions.EF_CONSTRUCTION !== undefined) {
218249
args.push('EF_CONSTRUCTION', fieldOptions.EF_CONSTRUCTION.toString());
219250
}
220251

221-
if (fieldOptions.EF_RUNTIME) {
252+
if (fieldOptions.EF_RUNTIME !== undefined) {
222253
args.push('EF_RUNTIME', fieldOptions.EF_RUNTIME.toString());
223254
}
224255

256+
break;
257+
258+
case SCHEMA_VECTOR_FIELD_ALGORITHM['VAMANA']:
259+
if (fieldOptions.COMPRESSION) {
260+
args.push('COMPRESSION', fieldOptions.COMPRESSION);
261+
}
262+
263+
if (fieldOptions.CONSTRUCTION_WINDOW_SIZE !== undefined) {
264+
args.push('CONSTRUCTION_WINDOW_SIZE', fieldOptions.CONSTRUCTION_WINDOW_SIZE.toString());
265+
}
266+
267+
if (fieldOptions.GRAPH_MAX_DEGREE !== undefined) {
268+
args.push('GRAPH_MAX_DEGREE', fieldOptions.GRAPH_MAX_DEGREE.toString());
269+
}
270+
271+
if (fieldOptions.SEARCH_WINDOW_SIZE !== undefined) {
272+
args.push('SEARCH_WINDOW_SIZE', fieldOptions.SEARCH_WINDOW_SIZE.toString());
273+
}
274+
275+
if (fieldOptions.EPSILON !== undefined) {
276+
args.push('EPSILON', fieldOptions.EPSILON.toString());
277+
}
278+
279+
if (fieldOptions.TRAINING_THRESHOLD !== undefined) {
280+
args.push('TRAINING_THRESHOLD', fieldOptions.TRAINING_THRESHOLD.toString());
281+
}
282+
283+
if (fieldOptions.REDUCE !== undefined) {
284+
args.push('REDUCE', fieldOptions.REDUCE.toString());
285+
}
286+
225287
break;
226288
}
227289
parser.pushVariadicWithLength(args);

0 commit comments

Comments
 (0)