164 lines
5.9 KiB
JavaScript
164 lines
5.9 KiB
JavaScript
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
exports.QdrantTranslator = void 0;
|
||
|
const structured_query_1 = require("@langchain/core/structured_query");
|
||
|
/**
|
||
|
* A class that translates or converts `StructuredQuery` to equivalent Qdrant filters.
|
||
|
* @example
|
||
|
* ```typescript
|
||
|
* const selfQueryRetriever = new SelfQueryRetriever({
|
||
|
* llm: new ChatOpenAI(),
|
||
|
* vectorStore: new QdrantVectorStore(...),
|
||
|
* documentContents: "Brief summary of a movie",
|
||
|
* attributeInfo: [],
|
||
|
* structuredQueryTranslator: new QdrantTranslator(),
|
||
|
* });
|
||
|
*
|
||
|
* const relevantDocuments = await selfQueryRetriever.getRelevantDocuments(
|
||
|
* "Which movies are rated higher than 8.5?",
|
||
|
* );
|
||
|
* ```
|
||
|
*/
|
||
|
class QdrantTranslator extends structured_query_1.BaseTranslator {
|
||
|
constructor() {
|
||
|
super(...arguments);
|
||
|
Object.defineProperty(this, "allowedOperators", {
|
||
|
enumerable: true,
|
||
|
configurable: true,
|
||
|
writable: true,
|
||
|
value: [structured_query_1.Operators.and, structured_query_1.Operators.or, structured_query_1.Operators.not]
|
||
|
});
|
||
|
Object.defineProperty(this, "allowedComparators", {
|
||
|
enumerable: true,
|
||
|
configurable: true,
|
||
|
writable: true,
|
||
|
value: [
|
||
|
structured_query_1.Comparators.eq,
|
||
|
structured_query_1.Comparators.ne,
|
||
|
structured_query_1.Comparators.lt,
|
||
|
structured_query_1.Comparators.lte,
|
||
|
structured_query_1.Comparators.gt,
|
||
|
structured_query_1.Comparators.gte,
|
||
|
]
|
||
|
});
|
||
|
}
|
||
|
/**
|
||
|
* Visits an operation and returns a QdrantFilter.
|
||
|
* @param operation The operation to visit.
|
||
|
* @returns A QdrantFilter.
|
||
|
*/
|
||
|
visitOperation(operation) {
|
||
|
const args = operation.args?.map((arg) => arg.accept(this));
|
||
|
const operator = {
|
||
|
[structured_query_1.Operators.and]: "must",
|
||
|
[structured_query_1.Operators.or]: "should",
|
||
|
[structured_query_1.Operators.not]: "must_not",
|
||
|
}[operation.operator];
|
||
|
return {
|
||
|
[operator]: args,
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* Visits a comparison and returns a QdrantCondition.
|
||
|
* The value is casted to the correct type.
|
||
|
* The attribute is prefixed with "metadata.",
|
||
|
* since metadata is nested in the Qdrant payload.
|
||
|
* @param comparison The comparison to visit.
|
||
|
* @returns A QdrantCondition.
|
||
|
*/
|
||
|
visitComparison(comparison) {
|
||
|
const attribute = `metadata.${comparison.attribute}`;
|
||
|
const value = (0, structured_query_1.castValue)(comparison.value);
|
||
|
if (comparison.comparator === "eq") {
|
||
|
return {
|
||
|
key: attribute,
|
||
|
match: {
|
||
|
value,
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
else if (comparison.comparator === "ne") {
|
||
|
return {
|
||
|
key: attribute,
|
||
|
match: {
|
||
|
except: [value],
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
if (!(0, structured_query_1.isInt)(value) && !(0, structured_query_1.isFloat)(value)) {
|
||
|
throw new Error("Value for gt, gte, lt, lte must be a number");
|
||
|
}
|
||
|
// For gt, gte, lt, lte, we need to use the range filter
|
||
|
return {
|
||
|
key: attribute,
|
||
|
range: {
|
||
|
[comparison.comparator]: value,
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
/**
|
||
|
* Visits a structured query and returns a VisitStructuredQueryOutput.
|
||
|
* If the query has a filter, it is visited.
|
||
|
* @param query The structured query to visit.
|
||
|
* @returns An instance of VisitStructuredQueryOutput.
|
||
|
*/
|
||
|
visitStructuredQuery(query) {
|
||
|
let nextArg = {};
|
||
|
if (query.filter) {
|
||
|
nextArg = {
|
||
|
filter: { must: [query.filter.accept(this)] },
|
||
|
};
|
||
|
}
|
||
|
return nextArg;
|
||
|
}
|
||
|
/**
|
||
|
* Merges two filters into one. If both filters are empty, returns
|
||
|
* undefined. If one filter is empty or the merge type is 'replace',
|
||
|
* returns the other filter. If the merge type is 'and' or 'or', returns a
|
||
|
* new filter with the merged results. Throws an error for unknown merge
|
||
|
* types.
|
||
|
* @param defaultFilter The default filter to merge.
|
||
|
* @param generatedFilter The generated filter to merge.
|
||
|
* @param mergeType The type of merge to perform. Can be 'and', 'or', or 'replace'. Defaults to 'and'.
|
||
|
* @param forceDefaultFilter If true, the default filter is always returned if the generated filter is empty. Defaults to false.
|
||
|
* @returns A merged QdrantFilter, or undefined if both filters are empty.
|
||
|
*/
|
||
|
mergeFilters(defaultFilter, generatedFilter, mergeType = "and", forceDefaultFilter = false) {
|
||
|
if ((0, structured_query_1.isFilterEmpty)(defaultFilter) && (0, structured_query_1.isFilterEmpty)(generatedFilter)) {
|
||
|
return undefined;
|
||
|
}
|
||
|
if ((0, structured_query_1.isFilterEmpty)(defaultFilter) || mergeType === "replace") {
|
||
|
if ((0, structured_query_1.isFilterEmpty)(generatedFilter)) {
|
||
|
return undefined;
|
||
|
}
|
||
|
return generatedFilter;
|
||
|
}
|
||
|
if ((0, structured_query_1.isFilterEmpty)(generatedFilter)) {
|
||
|
if (forceDefaultFilter) {
|
||
|
return defaultFilter;
|
||
|
}
|
||
|
if (mergeType === "and") {
|
||
|
return undefined;
|
||
|
}
|
||
|
return defaultFilter;
|
||
|
}
|
||
|
if (mergeType === "and") {
|
||
|
return {
|
||
|
must: [defaultFilter, generatedFilter],
|
||
|
};
|
||
|
}
|
||
|
else if (mergeType === "or") {
|
||
|
return {
|
||
|
should: [defaultFilter, generatedFilter],
|
||
|
};
|
||
|
}
|
||
|
else {
|
||
|
throw new Error("Unknown merge type");
|
||
|
}
|
||
|
}
|
||
|
formatFunction() {
|
||
|
throw new Error("Not implemented");
|
||
|
}
|
||
|
}
|
||
|
exports.QdrantTranslator = QdrantTranslator;
|