1
0
mirror of https://github.com/redis/node-redis.git synced 2025-12-12 21:21:15 +03:00

Add support svs vamana index creation (#3025)

* 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

* feat(search): add additional VAMANA compression algorithms

- Add LVQ4, LVQ4x4, LVQ4x8, LeanVec4x8, and LeanVec8x8 compression options
- Update test to use LeanVec4x8 compression algorithm

* chore: update Redis version from 8.2-rc1 to 8.2-rc2-pre
This commit is contained in:
Pavel Pashov
2025-08-04 12:07:18 +03:00
committed by GitHub
parent e2d4b43e39
commit 66638fc903
10 changed files with 193 additions and 16 deletions

View File

@@ -1,6 +1,6 @@
import { strict as assert } from 'node:assert';
import testUtils, { GLOBAL } from '../test-utils';
import CREATE, { SCHEMA_FIELD_TYPE, SCHEMA_TEXT_FIELD_PHONETIC, SCHEMA_VECTOR_FIELD_ALGORITHM, REDISEARCH_LANGUAGE } from './CREATE';
import CREATE, { SCHEMA_FIELD_TYPE, SCHEMA_TEXT_FIELD_PHONETIC, SCHEMA_VECTOR_FIELD_ALGORITHM, REDISEARCH_LANGUAGE, VAMANA_COMPRESSION_ALGORITHM } from './CREATE';
import { parseArgs } from '@redis/client/lib/commands/generic-transformers';
describe('FT.CREATE', () => {
@@ -206,6 +206,33 @@ describe('FT.CREATE', () => {
]
);
});
it('VAMANA algorithm', () => {
assert.deepEqual(
parseArgs(CREATE, 'index', {
field: {
type: SCHEMA_FIELD_TYPE.VECTOR,
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
TYPE: "FLOAT32",
COMPRESSION: VAMANA_COMPRESSION_ALGORITHM.LVQ8,
DIM: 1024,
DISTANCE_METRIC: 'COSINE',
CONSTRUCTION_WINDOW_SIZE: 300,
GRAPH_MAX_DEGREE: 128,
SEARCH_WINDOW_SIZE: 20,
EPSILON: 0.02,
TRAINING_THRESHOLD: 20480,
REDUCE: 512,
}
}),
[
'FT.CREATE', 'index', 'SCHEMA', 'field', 'VECTOR', 'SVS-VAMANA', '20', 'TYPE',
'FLOAT32', 'DIM', '1024', 'DISTANCE_METRIC', 'COSINE', 'COMPRESSION', 'LVQ8',
'CONSTRUCTION_WINDOW_SIZE', '300', 'GRAPH_MAX_DEGREE', '128', 'SEARCH_WINDOW_SIZE', '20',
'EPSILON', '0.02', 'TRAINING_THRESHOLD', '20480', 'REDUCE', '512'
]
);
});
});
describe('GEOSHAPE', () => {
@@ -556,4 +583,87 @@ describe('FT.CREATE', () => {
"OK"
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClientIfVersionWithinRange([[8, 2], 'LATEST'], 'client.ft.create vector svs-vamana', async client => {
assert.equal(
await client.ft.create("index_svs_vamana_min_config", {
field: {
type: SCHEMA_FIELD_TYPE.VECTOR,
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
TYPE: "FLOAT32",
DIM: 768,
DISTANCE_METRIC: 'L2',
},
}),
"OK"
);
assert.equal(
await client.ft.create("index_svs_vamana_no_compression", {
field: {
type: SCHEMA_FIELD_TYPE.VECTOR,
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
TYPE: "FLOAT32",
DIM: 512,
DISTANCE_METRIC: 'L2',
CONSTRUCTION_WINDOW_SIZE: 200,
GRAPH_MAX_DEGREE: 64,
SEARCH_WINDOW_SIZE: 50,
EPSILON: 0.01
},
}),
"OK"
);
assert.equal(
await client.ft.create("index_svs_vamana_compression", {
field: {
type: SCHEMA_FIELD_TYPE.VECTOR,
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
TYPE: "FLOAT32",
COMPRESSION: VAMANA_COMPRESSION_ALGORITHM.LeanVec4x8,
DIM: 1024,
DISTANCE_METRIC: 'COSINE',
CONSTRUCTION_WINDOW_SIZE: 300,
GRAPH_MAX_DEGREE: 128,
SEARCH_WINDOW_SIZE: 20,
EPSILON: 0.02,
TRAINING_THRESHOLD: 20480,
REDUCE: 512,
},
}),
"OK"
);
assert.equal(
await client.ft.create("index_svs_vamana_float16", {
field: {
type: SCHEMA_FIELD_TYPE.VECTOR,
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
TYPE: "FLOAT16",
DIM: 128,
DISTANCE_METRIC: 'IP',
},
}),
"OK"
);
await assert.rejects(
client.ft.create("index_svs_vamana_invalid_config", {
field: {
type: SCHEMA_FIELD_TYPE.VECTOR,
ALGORITHM: SCHEMA_VECTOR_FIELD_ALGORITHM.VAMANA,
TYPE: "FLOAT32",
DIM: 2,
DISTANCE_METRIC: 'L2',
CONSTRUCTION_WINDOW_SIZE: 200,
GRAPH_MAX_DEGREE: 64,
SEARCH_WINDOW_SIZE: 50,
EPSILON: 0.01,
// TRAINING_THRESHOLD should error without COMPRESSION
TRAINING_THRESHOLD: 2048
},
}),
)
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -54,7 +54,11 @@ interface SchemaTagField extends SchemaCommonField<typeof SCHEMA_FIELD_TYPE['TAG
export const SCHEMA_VECTOR_FIELD_ALGORITHM = {
FLAT: 'FLAT',
HNSW: 'HNSW'
HNSW: 'HNSW',
/**
* available since 8.2
*/
VAMANA: 'SVS-VAMANA'
} as const;
export type SchemaVectorFieldAlgorithm = typeof SCHEMA_VECTOR_FIELD_ALGORITHM[keyof typeof SCHEMA_VECTOR_FIELD_ALGORITHM];
@@ -79,6 +83,37 @@ interface SchemaHNSWVectorField extends SchemaVectorField {
EF_RUNTIME?: number;
}
export const VAMANA_COMPRESSION_ALGORITHM = {
LVQ4: 'LVQ4',
LVQ8: 'LVQ8',
LVQ4x4: 'LVQ4x4',
LVQ4x8: 'LVQ4x8',
LeanVec4x8: 'LeanVec4x8',
LeanVec8x8: 'LeanVec8x8'
} as const;
export type VamanaCompressionAlgorithm =
typeof VAMANA_COMPRESSION_ALGORITHM[keyof typeof VAMANA_COMPRESSION_ALGORITHM];
interface SchemaVAMANAVectorField extends SchemaVectorField {
ALGORITHM: typeof SCHEMA_VECTOR_FIELD_ALGORITHM['VAMANA'];
TYPE: 'FLOAT16' | 'FLOAT32';
// VAMANA-specific parameters
COMPRESSION?: VamanaCompressionAlgorithm;
CONSTRUCTION_WINDOW_SIZE?: number;
GRAPH_MAX_DEGREE?: number;
SEARCH_WINDOW_SIZE?: number;
EPSILON?: number;
/**
* applicable only with COMPRESSION
*/
TRAINING_THRESHOLD?: number;
/**
* applicable only with LeanVec COMPRESSION
*/
REDUCE?: number;
}
export const SCHEMA_GEO_SHAPE_COORD_SYSTEM = {
SPHERICAL: 'SPHERICAL',
FLAT: 'FLAT'
@@ -98,6 +133,7 @@ export interface RediSearchSchema {
SchemaTagField |
SchemaFlatVectorField |
SchemaHNSWVectorField |
SchemaVAMANAVectorField |
SchemaGeoShapeField |
SchemaFieldType
);
@@ -142,7 +178,7 @@ export function parseSchema(parser: CommandParser, schema: RediSearchSchema) {
parser.push('NOSTEM');
}
if (fieldOptions.WEIGHT) {
if (fieldOptions.WEIGHT !== undefined) {
parser.push('WEIGHT', fieldOptions.WEIGHT.toString());
}
@@ -197,31 +233,62 @@ export function parseSchema(parser: CommandParser, schema: RediSearchSchema) {
'DISTANCE_METRIC', fieldOptions.DISTANCE_METRIC
);
if (fieldOptions.INITIAL_CAP) {
if (fieldOptions.INITIAL_CAP !== undefined) {
args.push('INITIAL_CAP', fieldOptions.INITIAL_CAP.toString());
}
switch (fieldOptions.ALGORITHM) {
case SCHEMA_VECTOR_FIELD_ALGORITHM.FLAT:
if (fieldOptions.BLOCK_SIZE) {
if (fieldOptions.BLOCK_SIZE !== undefined) {
args.push('BLOCK_SIZE', fieldOptions.BLOCK_SIZE.toString());
}
break;
case SCHEMA_VECTOR_FIELD_ALGORITHM.HNSW:
if (fieldOptions.M) {
if (fieldOptions.M !== undefined) {
args.push('M', fieldOptions.M.toString());
}
if (fieldOptions.EF_CONSTRUCTION) {
if (fieldOptions.EF_CONSTRUCTION !== undefined) {
args.push('EF_CONSTRUCTION', fieldOptions.EF_CONSTRUCTION.toString());
}
if (fieldOptions.EF_RUNTIME) {
if (fieldOptions.EF_RUNTIME !== undefined) {
args.push('EF_RUNTIME', fieldOptions.EF_RUNTIME.toString());
}
break;
case SCHEMA_VECTOR_FIELD_ALGORITHM['VAMANA']:
if (fieldOptions.COMPRESSION) {
args.push('COMPRESSION', fieldOptions.COMPRESSION);
}
if (fieldOptions.CONSTRUCTION_WINDOW_SIZE !== undefined) {
args.push('CONSTRUCTION_WINDOW_SIZE', fieldOptions.CONSTRUCTION_WINDOW_SIZE.toString());
}
if (fieldOptions.GRAPH_MAX_DEGREE !== undefined) {
args.push('GRAPH_MAX_DEGREE', fieldOptions.GRAPH_MAX_DEGREE.toString());
}
if (fieldOptions.SEARCH_WINDOW_SIZE !== undefined) {
args.push('SEARCH_WINDOW_SIZE', fieldOptions.SEARCH_WINDOW_SIZE.toString());
}
if (fieldOptions.EPSILON !== undefined) {
args.push('EPSILON', fieldOptions.EPSILON.toString());
}
if (fieldOptions.TRAINING_THRESHOLD !== undefined) {
args.push('TRAINING_THRESHOLD', fieldOptions.TRAINING_THRESHOLD.toString());
}
if (fieldOptions.REDUCE !== undefined) {
args.push('REDUCE', fieldOptions.REDUCE.toString());
}
break;
}
parser.pushVariadicWithLength(args);