Files
rspade_system/node_modules/@redis/search/dist/lib/commands/HYBRID.js

273 lines
9.3 KiB
JavaScript
Executable File

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FT_HYBRID_COMBINE_METHOD = exports.FT_HYBRID_VECTOR_METHOD = void 0;
const generic_transformers_1 = require("@redis/client/dist/lib/commands/generic-transformers");
const SEARCH_1 = require("./SEARCH");
const AGGREGATE_1 = require("./AGGREGATE");
/**
* Vector search method configuration - either KNN or RANGE.
*/
exports.FT_HYBRID_VECTOR_METHOD = {
/** K-Nearest Neighbors search configuration */
KNN: "KNN",
/** Range-based vector search configuration */
RANGE: "RANGE",
};
/**
* Score fusion method type constants for combining search results.
*/
exports.FT_HYBRID_COMBINE_METHOD = {
/** Reciprocal Rank Fusion */
RRF: "RRF",
/** Linear combination with ALPHA and BETA weights */
LINEAR: "LINEAR",
};
function parseSearchExpression(parser, search) {
parser.push("SEARCH", search.query);
if (search.SCORER) {
parser.push("SCORER", search.SCORER);
}
if (search.YIELD_SCORE_AS) {
parser.push("YIELD_SCORE_AS", search.YIELD_SCORE_AS);
}
}
function parseVectorExpression(parser, vsim) {
parser.push("VSIM", vsim.field, vsim.vector);
if (vsim.method) {
if (vsim.method.type === exports.FT_HYBRID_VECTOR_METHOD.KNN) {
let argsCount = 2;
if (vsim.method.EF_RUNTIME !== undefined) {
argsCount += 2;
}
parser.push("KNN", argsCount.toString(), "K", vsim.method.K.toString());
if (vsim.method.EF_RUNTIME !== undefined) {
parser.push("EF_RUNTIME", vsim.method.EF_RUNTIME.toString());
}
}
if (vsim.method.type === exports.FT_HYBRID_VECTOR_METHOD.RANGE) {
let argsCount = 2;
if (vsim.method.EPSILON !== undefined) {
argsCount += 2;
}
parser.push("RANGE", argsCount.toString(), "RADIUS", vsim.method.RADIUS.toString());
if (vsim.method.EPSILON !== undefined) {
parser.push("EPSILON", vsim.method.EPSILON.toString());
}
}
}
if (vsim.FILTER) {
parser.push("FILTER", vsim.FILTER);
}
if (vsim.YIELD_SCORE_AS) {
parser.push("YIELD_SCORE_AS", vsim.YIELD_SCORE_AS);
}
}
function parseCombineMethod(parser, combine) {
if (!combine)
return;
parser.push("COMBINE");
if (combine.method.type === exports.FT_HYBRID_COMBINE_METHOD.RRF) {
// Calculate argsCount: 2 per optional (WINDOW, CONSTANT, YIELD_SCORE_AS)
let argsCount = 0;
if (combine.method.WINDOW !== undefined) {
argsCount += 2;
}
if (combine.method.CONSTANT !== undefined) {
argsCount += 2;
}
if (combine.YIELD_SCORE_AS) {
argsCount += 2;
}
parser.push("RRF", argsCount.toString());
if (combine.method.WINDOW !== undefined) {
parser.push("WINDOW", combine.method.WINDOW.toString());
}
if (combine.method.CONSTANT !== undefined) {
parser.push("CONSTANT", combine.method.CONSTANT.toString());
}
}
if (combine.method.type === exports.FT_HYBRID_COMBINE_METHOD.LINEAR) {
// Calculate argsCount: 2 per optional (ALPHA, BETA, WINDOW, YIELD_SCORE_AS)
let argsCount = 0;
if (combine.method.ALPHA !== undefined) {
argsCount += 2;
}
if (combine.method.BETA !== undefined) {
argsCount += 2;
}
if (combine.method.WINDOW !== undefined) {
argsCount += 2;
}
if (combine.YIELD_SCORE_AS) {
argsCount += 2;
}
parser.push("LINEAR", argsCount.toString());
if (combine.method.ALPHA !== undefined) {
parser.push("ALPHA", combine.method.ALPHA.toString());
}
if (combine.method.BETA !== undefined) {
parser.push("BETA", combine.method.BETA.toString());
}
if (combine.method.WINDOW !== undefined) {
parser.push("WINDOW", combine.method.WINDOW.toString());
}
}
if (combine.YIELD_SCORE_AS) {
parser.push("YIELD_SCORE_AS", combine.YIELD_SCORE_AS);
}
}
function parseApply(parser, apply) {
parser.push("APPLY", apply.expression);
if (apply.AS) {
parser.push("AS", apply.AS);
}
}
function parseHybridOptions(parser, options) {
parseSearchExpression(parser, options.SEARCH);
parseVectorExpression(parser, options.VSIM);
if (options.COMBINE) {
parseCombineMethod(parser, options.COMBINE);
}
if (options.LOAD) {
if (options.LOAD === "*") {
parser.push("LOAD", "*");
}
else {
(0, generic_transformers_1.parseOptionalVariadicArgument)(parser, "LOAD", options.LOAD);
}
}
if (options.GROUPBY) {
(0, generic_transformers_1.parseOptionalVariadicArgument)(parser, "GROUPBY", options.GROUPBY.fields);
if (options.GROUPBY.REDUCE) {
const reducers = Array.isArray(options.GROUPBY.REDUCE)
? options.GROUPBY.REDUCE
: [options.GROUPBY.REDUCE];
for (const reducer of reducers) {
(0, AGGREGATE_1.parseGroupByReducer)(parser, reducer);
}
}
}
if (options.APPLY) {
const applies = Array.isArray(options.APPLY)
? options.APPLY
: [options.APPLY];
for (const apply of applies) {
parseApply(parser, apply);
}
}
if (options.SORTBY) {
const sortByArgsCount = options.SORTBY.fields.reduce((acc, field) => {
if (field.direction) {
return acc + 2;
}
return acc + 1;
}, 0);
parser.push("SORTBY", sortByArgsCount.toString());
for (const sortField of options.SORTBY.fields) {
parser.push(sortField.field);
if (sortField.direction) {
parser.push(sortField.direction);
}
}
}
if (options.NOSORT) {
parser.push("NOSORT");
}
if (options.FILTER) {
parser.push("FILTER", options.FILTER);
}
if (options.LIMIT) {
parser.push("LIMIT", options.LIMIT.offset.toString(), options.LIMIT.count.toString());
}
const hasParams = options.PARAMS && Object.keys(options.PARAMS).length > 0;
(0, SEARCH_1.parseParamsArgument)(parser, hasParams ? options.PARAMS : undefined);
if (options.TIMEOUT !== undefined) {
parser.push("TIMEOUT", options.TIMEOUT.toString());
}
}
exports.default = {
NOT_KEYED_COMMAND: true,
IS_READ_ONLY: true,
/**
* Performs a hybrid search combining multiple search expressions.
* Supports multiple SEARCH and VECTOR expressions with various fusion methods.
*
* @experimental
* NOTE: FT.Hybrid is still in experimental state
* It's behaviour and function signature may change
*
* @param parser - The command parser
* @param index - The index name to search
* @param options - Hybrid search options including:
* - SEARCH: Text search expression with optional scoring
* - VSIM: Vector similarity expression with KNN/RANGE methods
* - COMBINE: Fusion method (RRF, LINEAR)
* - Post-processing operations: LOAD, GROUPBY, APPLY, SORTBY, FILTER
* - Tunable options: LIMIT, PARAMS, TIMEOUT
*/
parseCommand(parser, index, options) {
parser.push("FT.HYBRID", index);
parseHybridOptions(parser, options);
},
transformReply: {
2: (reply) => {
return transformHybridSearchResults(reply);
},
3: undefined,
},
unstableResp3: true,
};
function transformHybridSearchResults(reply) {
// FT.HYBRID returns a map-like structure as flat array:
// ['total_results', N, 'results', [...], 'warnings', [...], 'execution_time', 'X.XXX']
const replyMap = parseReplyMap(reply);
const totalResults = replyMap["total_results"] ?? 0;
const rawResults = replyMap["results"] ?? [];
const warnings = replyMap["warnings"] ?? [];
const executionTime = replyMap["execution_time"]
? Number.parseFloat(replyMap["execution_time"])
: 0;
const results = [];
for (const result of rawResults) {
// Each result is a flat key-value array like FT.AGGREGATE: ['field1', 'value1', 'field2', 'value2', ...]
const resultMap = parseReplyMap(result);
const doc = Object.create(null);
// Add all other fields from the result
for (const [key, value] of Object.entries(resultMap)) {
if (key === "$") {
// JSON document - parse and merge
try {
Object.assign(doc, JSON.parse(value));
}
catch {
doc[key] = value;
}
}
else {
doc[key] = value;
}
}
results.push(doc);
}
return {
totalResults,
executionTime,
warnings,
results,
};
}
function parseReplyMap(reply) {
const map = {};
if (!Array.isArray(reply)) {
return map;
}
for (let i = 0; i < reply.length; i += 2) {
const key = reply[i];
const value = reply[i + 1];
if (typeof key === "string") {
map[key] = value;
}
}
return map;
}
//# sourceMappingURL=HYBRID.js.map