You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-12-15 23:55:38 +03:00
350 lines
9.3 KiB
TypeScript
350 lines
9.3 KiB
TypeScript
import { strict as assert } from 'node:assert';
|
|
import testUtils, { GLOBAL } from '../test-utils';
|
|
import HYBRID from './HYBRID';
|
|
import { BasicCommandParser } from '@redis/client/lib/client/parser';
|
|
|
|
describe('FT.HYBRID', () => {
|
|
describe('parseCommand', () => {
|
|
it('minimal command', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index');
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
['FT.HYBRID', 'index']
|
|
);
|
|
});
|
|
|
|
|
|
it('with SEARCH expression', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
SEARCH: {
|
|
query: '@description: bikes'
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
['FT.HYBRID', 'index', 'SEARCH', '@description: bikes']
|
|
);
|
|
});
|
|
|
|
it('with SEARCH expression and SCORER', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
SEARCH: {
|
|
query: '@description: bikes',
|
|
SCORER: {
|
|
algorithm: 'TFIDF.DOCNORM',
|
|
params: ['param1', 'param2']
|
|
},
|
|
YIELD_SCORE_AS: 'search_score'
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'SEARCH', '@description: bikes',
|
|
'SCORER', 'TFIDF.DOCNORM', 'param1', 'param2',
|
|
'YIELD_SCORE_AS', 'search_score'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('with VSIM expression and KNN method', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
VSIM: {
|
|
field: '@vector_field',
|
|
vectorData: 'BLOB_DATA',
|
|
method: {
|
|
KNN: {
|
|
K: 10,
|
|
EF_RUNTIME: 50,
|
|
YIELD_DISTANCE_AS: 'vector_dist'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA',
|
|
'KNN', '1', 'K', '10', 'EF_RUNTIME', '50', 'YIELD_DISTANCE_AS', 'vector_dist'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('with VSIM expression and RANGE method', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
VSIM: {
|
|
field: '@vector_field',
|
|
vectorData: 'BLOB_DATA',
|
|
method: {
|
|
RANGE: {
|
|
RADIUS: 0.5,
|
|
EPSILON: 0.01,
|
|
YIELD_DISTANCE_AS: 'vector_dist'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA',
|
|
'RANGE', '1', 'RADIUS', '0.5', 'EPSILON', '0.01', 'YIELD_DISTANCE_AS', 'vector_dist'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('with VSIM expression and FILTER', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
VSIM: {
|
|
field: '@vector_field',
|
|
vectorData: 'BLOB_DATA',
|
|
FILTER: {
|
|
expression: '@category:{bikes}',
|
|
POLICY: 'BATCHES',
|
|
BATCHES: {
|
|
BATCH_SIZE: 100
|
|
}
|
|
},
|
|
YIELD_SCORE_AS: 'vsim_score'
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA',
|
|
'FILTER', '@category:{bikes}', 'POLICY', 'BATCHES', 'BATCHES', 'BATCH_SIZE', '100',
|
|
'YIELD_SCORE_AS', 'vsim_score'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('with RRF COMBINE method', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
COMBINE: {
|
|
method: {
|
|
RRF: {
|
|
count: 2,
|
|
WINDOW: 10,
|
|
CONSTANT: 60
|
|
}
|
|
},
|
|
YIELD_SCORE_AS: 'combined_score'
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'COMBINE', 'RRF', '2', 'WINDOW', '10', 'CONSTANT', '60',
|
|
'YIELD_SCORE_AS', 'combined_score'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('with LINEAR COMBINE method', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
COMBINE: {
|
|
method: {
|
|
LINEAR: {
|
|
count: 2,
|
|
ALPHA: 0.7,
|
|
BETA: 0.3
|
|
}
|
|
}
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'COMBINE', 'LINEAR', '2', 'ALPHA', '0.7', 'BETA', '0.3'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('with LOAD, SORTBY, and LIMIT', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
LOAD: ['field1', 'field2'],
|
|
SORTBY: {
|
|
count: 1,
|
|
fields: [
|
|
{ field: 'score', direction: 'DESC' }
|
|
]
|
|
},
|
|
LIMIT: {
|
|
offset: 0,
|
|
num: 10
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'LOAD', '2', 'field1', 'field2',
|
|
'SORTBY', '1', 'score', 'DESC', 'LIMIT', '0', '10'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('with GROUPBY and REDUCE', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
GROUPBY: {
|
|
fields: ['@category'],
|
|
REDUCE: {
|
|
function: 'COUNT',
|
|
count: 0,
|
|
args: []
|
|
}
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'GROUPBY', '1', '@category', 'REDUCE', 'COUNT', '0'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('with APPLY', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
APPLY: {
|
|
expression: '@score * 2',
|
|
AS: 'double_score'
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
['FT.HYBRID', 'index', 'APPLY', '@score * 2', 'AS', 'double_score']
|
|
);
|
|
});
|
|
|
|
it('with FILTER and post-processing', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
FILTER: '@price:[100 500]'
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
['FT.HYBRID', 'index', 'FILTER', '@price:[100 500]']
|
|
);
|
|
});
|
|
|
|
it('with PARAMS', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
PARAMS: {
|
|
query_vector: 'BLOB_DATA',
|
|
min_price: 100
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'PARAMS', '4', 'query_vector', 'BLOB_DATA', 'min_price', '100'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('with EXPLAINSCORE and TIMEOUT', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
EXPLAINSCORE: true,
|
|
TIMEOUT: 5000
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
['FT.HYBRID', 'index', 'EXPLAINSCORE', 'TIMEOUT', '5000']
|
|
);
|
|
});
|
|
|
|
it('with WITHCURSOR', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
WITHCURSOR: {
|
|
COUNT: 100,
|
|
MAXIDLE: 300000
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index', 'WITHCURSOR', 'COUNT', '100', 'MAXIDLE', '300000'
|
|
]
|
|
);
|
|
});
|
|
|
|
it('complete example with all options', () => {
|
|
const parser = new BasicCommandParser();
|
|
HYBRID.parseCommand(parser, 'index', {
|
|
SEARCH: {
|
|
query: '@description: bikes',
|
|
SCORER: {
|
|
algorithm: 'TFIDF.DOCNORM'
|
|
},
|
|
YIELD_SCORE_AS: 'text_score'
|
|
},
|
|
VSIM: {
|
|
field: '@vector_field',
|
|
vectorData: '$query_vector',
|
|
method: {
|
|
KNN: {
|
|
K: 5
|
|
}
|
|
},
|
|
YIELD_SCORE_AS: 'vector_score'
|
|
},
|
|
COMBINE: {
|
|
method: {
|
|
RRF: {
|
|
count: 2,
|
|
CONSTANT: 60
|
|
}
|
|
},
|
|
YIELD_SCORE_AS: 'final_score'
|
|
},
|
|
LOAD: ['description', 'price'],
|
|
SORTBY: {
|
|
count: 1,
|
|
fields: [{ field: 'final_score', direction: 'DESC' }]
|
|
},
|
|
LIMIT: {
|
|
offset: 0,
|
|
num: 10
|
|
},
|
|
PARAMS: {
|
|
query_vector: 'BLOB_DATA'
|
|
}
|
|
});
|
|
assert.deepEqual(
|
|
parser.redisArgs,
|
|
[
|
|
'FT.HYBRID', 'index',
|
|
'SEARCH', '@description: bikes', 'SCORER', 'TFIDF.DOCNORM', 'YIELD_SCORE_AS', 'text_score',
|
|
'VSIM', '@vector_field', '$query_vector', 'KNN', '1', 'K', '5', 'YIELD_SCORE_AS', 'vector_score',
|
|
'COMBINE', 'RRF', '2', 'CONSTANT', '60', 'YIELD_SCORE_AS', 'final_score',
|
|
'LOAD', '2', 'description', 'price',
|
|
'SORTBY', '1', 'final_score', 'DESC',
|
|
'LIMIT', '0', '10',
|
|
'PARAMS', '2', 'query_vector', 'BLOB_DATA'
|
|
]
|
|
);
|
|
});
|
|
});
|
|
|
|
// Integration tests would need to be added when RediSearch supports FT.HYBRID
|
|
// For now, we'll skip them as this is a new command that may not be available yet
|
|
describe.skip('client.ft.hybrid', () => {
|
|
testUtils.testWithClient('basic hybrid search', async client => {
|
|
// This would require a test index and data setup
|
|
// similar to how other FT commands are tested
|
|
}, GLOBAL.SERVERS.OPEN);
|
|
});
|
|
}); |