You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-12-11 09:22:35 +03:00
fix: Hybrid Search, minor changes, added experimental notice (#3132)
* Hybrid Search, minor changes, added experimental notice * fixed twot tests
This commit is contained in:
@@ -10,20 +10,10 @@ describe('FT.HYBRID', () => {
|
||||
HYBRID.parseCommand(parser, 'index');
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['FT.HYBRID', 'index', '2', 'DIALECT', '2']
|
||||
['FT.HYBRID', 'index']
|
||||
);
|
||||
});
|
||||
|
||||
it('with count expressions', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
HYBRID.parseCommand(parser, 'index', {
|
||||
countExpressions: 3
|
||||
});
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['FT.HYBRID', 'index', '3', 'DIALECT', '2']
|
||||
);
|
||||
});
|
||||
|
||||
it('with SEARCH expression', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
@@ -34,7 +24,7 @@ describe('FT.HYBRID', () => {
|
||||
});
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['FT.HYBRID', 'index', '2', 'SEARCH', '@description: bikes', 'DIALECT', '2']
|
||||
['FT.HYBRID', 'index', 'SEARCH', '@description: bikes']
|
||||
);
|
||||
});
|
||||
|
||||
@@ -53,9 +43,9 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'SEARCH', '@description: bikes',
|
||||
'FT.HYBRID', 'index', 'SEARCH', '@description: bikes',
|
||||
'SCORER', 'TFIDF.DOCNORM', 'param1', 'param2',
|
||||
'YIELD_SCORE_AS', 'search_score', 'DIALECT', '2'
|
||||
'YIELD_SCORE_AS', 'search_score'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -78,9 +68,8 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'VSIM', '@vector_field', 'BLOB_DATA',
|
||||
'KNN', '1', 'K', '10', 'EF_RUNTIME', '50', 'YIELD_DISTANCE_AS', 'vector_dist',
|
||||
'DIALECT', '2'
|
||||
'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA',
|
||||
'KNN', '1', 'K', '10', 'EF_RUNTIME', '50', 'YIELD_DISTANCE_AS', 'vector_dist'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -103,9 +92,8 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'VSIM', '@vector_field', 'BLOB_DATA',
|
||||
'RANGE', '1', 'RADIUS', '0.5', 'EPSILON', '0.01', 'YIELD_DISTANCE_AS', 'vector_dist',
|
||||
'DIALECT', '2'
|
||||
'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA',
|
||||
'RANGE', '1', 'RADIUS', '0.5', 'EPSILON', '0.01', 'YIELD_DISTANCE_AS', 'vector_dist'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -129,9 +117,9 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'VSIM', '@vector_field', 'BLOB_DATA',
|
||||
'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA',
|
||||
'FILTER', '@category:{bikes}', 'POLICY', 'BATCHES', 'BATCHES', 'BATCH_SIZE', '100',
|
||||
'YIELD_SCORE_AS', 'vsim_score', 'DIALECT', '2'
|
||||
'YIELD_SCORE_AS', 'vsim_score'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -153,8 +141,8 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'COMBINE', 'RRF', '2', 'WINDOW', '10', 'CONSTANT', '60',
|
||||
'YIELD_SCORE_AS', 'combined_score', 'DIALECT', '2'
|
||||
'FT.HYBRID', 'index', 'COMBINE', 'RRF', '2', 'WINDOW', '10', 'CONSTANT', '60',
|
||||
'YIELD_SCORE_AS', 'combined_score'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -175,8 +163,7 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'COMBINE', 'LINEAR', '2', 'ALPHA', '0.7', 'BETA', '0.3',
|
||||
'DIALECT', '2'
|
||||
'FT.HYBRID', 'index', 'COMBINE', 'LINEAR', '2', 'ALPHA', '0.7', 'BETA', '0.3'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -199,8 +186,8 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'LOAD', '2', 'field1', 'field2',
|
||||
'SORTBY', '1', 'score', 'DESC', 'LIMIT', '0', '10', 'DIALECT', '2'
|
||||
'FT.HYBRID', 'index', 'LOAD', '2', 'field1', 'field2',
|
||||
'SORTBY', '1', 'score', 'DESC', 'LIMIT', '0', '10'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -220,8 +207,7 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'GROUPBY', '1', '@category', 'REDUCE', 'COUNT', '0',
|
||||
'DIALECT', '2'
|
||||
'FT.HYBRID', 'index', 'GROUPBY', '1', '@category', 'REDUCE', 'COUNT', '0'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -236,7 +222,7 @@ describe('FT.HYBRID', () => {
|
||||
});
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['FT.HYBRID', 'index', '2', 'APPLY', '@score * 2', 'AS', 'double_score', 'DIALECT', '2']
|
||||
['FT.HYBRID', 'index', 'APPLY', '@score * 2', 'AS', 'double_score']
|
||||
);
|
||||
});
|
||||
|
||||
@@ -247,7 +233,7 @@ describe('FT.HYBRID', () => {
|
||||
});
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['FT.HYBRID', 'index', '2', 'FILTER', '@price:[100 500]', 'DIALECT', '2']
|
||||
['FT.HYBRID', 'index', 'FILTER', '@price:[100 500]']
|
||||
);
|
||||
});
|
||||
|
||||
@@ -262,8 +248,7 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'PARAMS', '4', 'query_vector', 'BLOB_DATA', 'min_price', '100',
|
||||
'DIALECT', '2'
|
||||
'FT.HYBRID', 'index', 'PARAMS', '4', 'query_vector', 'BLOB_DATA', 'min_price', '100'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -276,7 +261,7 @@ describe('FT.HYBRID', () => {
|
||||
});
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['FT.HYBRID', 'index', '2', 'EXPLAINSCORE', 'TIMEOUT', '5000', 'DIALECT', '2']
|
||||
['FT.HYBRID', 'index', 'EXPLAINSCORE', 'TIMEOUT', '5000']
|
||||
);
|
||||
});
|
||||
|
||||
@@ -291,8 +276,7 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2', 'WITHCURSOR', 'COUNT', '100', 'MAXIDLE', '300000',
|
||||
'DIALECT', '2'
|
||||
'FT.HYBRID', 'index', 'WITHCURSOR', 'COUNT', '100', 'MAXIDLE', '300000'
|
||||
]
|
||||
);
|
||||
});
|
||||
@@ -300,7 +284,6 @@ describe('FT.HYBRID', () => {
|
||||
it('complete example with all options', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
HYBRID.parseCommand(parser, 'index', {
|
||||
countExpressions: 2,
|
||||
SEARCH: {
|
||||
query: '@description: bikes',
|
||||
SCORER: {
|
||||
@@ -343,29 +326,17 @@ describe('FT.HYBRID', () => {
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
[
|
||||
'FT.HYBRID', 'index', '2',
|
||||
'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',
|
||||
'DIALECT', '2'
|
||||
'PARAMS', '2', 'query_vector', 'BLOB_DATA'
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('with custom DIALECT', () => {
|
||||
const parser = new BasicCommandParser();
|
||||
HYBRID.parseCommand(parser, 'index', {
|
||||
DIALECT: 3
|
||||
});
|
||||
assert.deepEqual(
|
||||
parser.redisArgs,
|
||||
['FT.HYBRID', 'index', '2', 'DIALECT', '3']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Integration tests would need to be added when RediSearch supports FT.HYBRID
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { CommandParser } from '@redis/client/dist/lib/client/parser';
|
||||
import { RedisArgument, Command, ReplyUnion } from '@redis/client/dist/lib/RESP/types';
|
||||
import { RedisVariadicArgument, parseOptionalVariadicArgument } from '@redis/client/dist/lib/commands/generic-transformers';
|
||||
import { DEFAULT_DIALECT } from '../dialect/default';
|
||||
import { FtSearchParams, parseParamsArgument } from './SEARCH';
|
||||
|
||||
export interface FtHybridSearchExpression {
|
||||
@@ -55,7 +54,6 @@ export interface FtHybridCombineMethod {
|
||||
}
|
||||
|
||||
export interface FtHybridOptions {
|
||||
countExpressions?: number;
|
||||
SEARCH?: FtHybridSearchExpression;
|
||||
VSIM?: FtHybridVectorExpression;
|
||||
COMBINE?: {
|
||||
@@ -94,7 +92,6 @@ export interface FtHybridOptions {
|
||||
COUNT?: number;
|
||||
MAXIDLE?: number;
|
||||
};
|
||||
DIALECT?: number;
|
||||
}
|
||||
|
||||
function parseSearchExpression(parser: CommandParser, search: FtHybridSearchExpression) {
|
||||
@@ -269,10 +266,6 @@ function parseHybridOptions(parser: CommandParser, options?: FtHybridOptions) {
|
||||
parser.push('MAXIDLE', options.WITHCURSOR.MAXIDLE.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.DIALECT) {
|
||||
parser.push('DIALECT', options.DIALECT.toString());
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -282,10 +275,12 @@ export default {
|
||||
* Performs a hybrid search combining multiple search expressions.
|
||||
* Supports multiple SEARCH and VECTOR expressions with various fusion methods.
|
||||
*
|
||||
* NOTE: FT.Hybrid is still in experimental state
|
||||
* It's behavioud and function signature may change`
|
||||
*
|
||||
* @param parser - The command parser
|
||||
* @param index - The index name to search
|
||||
* @param options - Hybrid search options including:
|
||||
* - countExpressions: Number of expressions (default 2)
|
||||
* - SEARCH: Text search expression with optional scoring
|
||||
* - VSIM: Vector similarity expression with KNN/RANGE methods
|
||||
* - COMBINE: Fusion method (RRF, LINEAR, FUNCTION)
|
||||
@@ -295,18 +290,8 @@ export default {
|
||||
parseCommand(parser: CommandParser, index: RedisArgument, options?: FtHybridOptions) {
|
||||
parser.push('FT.HYBRID', index);
|
||||
|
||||
if (options?.countExpressions !== undefined) {
|
||||
parser.push(options.countExpressions.toString());
|
||||
} else {
|
||||
parser.push('2'); // Default to 2 expressions
|
||||
}
|
||||
|
||||
parseHybridOptions(parser, options);
|
||||
|
||||
// Always add DIALECT at the end if not already added
|
||||
if (!options?.DIALECT) {
|
||||
parser.push('DIALECT', DEFAULT_DIALECT);
|
||||
}
|
||||
|
||||
},
|
||||
transformReply: {
|
||||
2: (reply: any): any => {
|
||||
|
||||
Reference in New Issue
Block a user