From 190bc260fd44d267e1aca8aa347797e5077db930 Mon Sep 17 00:00:00 2001 From: Hristo Temelski Date: Mon, 10 Nov 2025 10:57:19 +0200 Subject: [PATCH 1/2] Hybrid Search, minor changes, added experimental notice --- packages/search/lib/commands/HYBRID.spec.ts | 69 +++++++-------------- packages/search/lib/commands/HYBRID.ts | 23 ++----- 2 files changed, 25 insertions(+), 67 deletions(-) diff --git a/packages/search/lib/commands/HYBRID.spec.ts b/packages/search/lib/commands/HYBRID.spec.ts index 624c1b9c91..b6f09c9858 100644 --- a/packages/search/lib/commands/HYBRID.spec.ts +++ b/packages/search/lib/commands/HYBRID.spec.ts @@ -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,7 +68,7 @@ describe('FT.HYBRID', () => { assert.deepEqual( parser.redisArgs, [ - 'FT.HYBRID', 'index', '2', 'VSIM', '@vector_field', 'BLOB_DATA', + 'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA', 'KNN', '1', 'K', '10', 'EF_RUNTIME', '50', 'YIELD_DISTANCE_AS', 'vector_dist', 'DIALECT', '2' ] @@ -103,7 +93,7 @@ describe('FT.HYBRID', () => { assert.deepEqual( parser.redisArgs, [ - 'FT.HYBRID', 'index', '2', 'VSIM', '@vector_field', 'BLOB_DATA', + 'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA', 'RANGE', '1', 'RADIUS', '0.5', 'EPSILON', '0.01', 'YIELD_DISTANCE_AS', 'vector_dist', 'DIALECT', '2' ] @@ -129,9 +119,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 +143,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 +165,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 +188,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 +209,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 +224,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 +235,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 +250,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 +263,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 +278,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 +286,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 +328,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 diff --git a/packages/search/lib/commands/HYBRID.ts b/packages/search/lib/commands/HYBRID.ts index b94e7196da..c8b8ad0e9f 100644 --- a/packages/search/lib/commands/HYBRID.ts +++ b/packages/search/lib/commands/HYBRID.ts @@ -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 => { From fe3c924ac993d65b4a37204ac7bf4ce90932de92 Mon Sep 17 00:00:00 2001 From: Hristo Temelski Date: Mon, 10 Nov 2025 11:05:37 +0200 Subject: [PATCH 2/2] fixed twot tests --- packages/search/lib/commands/HYBRID.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/search/lib/commands/HYBRID.spec.ts b/packages/search/lib/commands/HYBRID.spec.ts index b6f09c9858..12f1ded6dc 100644 --- a/packages/search/lib/commands/HYBRID.spec.ts +++ b/packages/search/lib/commands/HYBRID.spec.ts @@ -69,8 +69,7 @@ describe('FT.HYBRID', () => { parser.redisArgs, [ 'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA', - 'KNN', '1', 'K', '10', 'EF_RUNTIME', '50', 'YIELD_DISTANCE_AS', 'vector_dist', - 'DIALECT', '2' + 'KNN', '1', 'K', '10', 'EF_RUNTIME', '50', 'YIELD_DISTANCE_AS', 'vector_dist' ] ); }); @@ -94,8 +93,7 @@ describe('FT.HYBRID', () => { parser.redisArgs, [ 'FT.HYBRID', 'index', 'VSIM', '@vector_field', 'BLOB_DATA', - 'RANGE', '1', 'RADIUS', '0.5', 'EPSILON', '0.01', 'YIELD_DISTANCE_AS', 'vector_dist', - 'DIALECT', '2' + 'RANGE', '1', 'RADIUS', '0.5', 'EPSILON', '0.01', 'YIELD_DISTANCE_AS', 'vector_dist' ] ); });