-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
community[minor]: feat: QdrantTranslator for self-query retrieval #5163
Merged
Merged
Changes from 1 commit
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
1e14441
feat: Qdrant self-query retriever
Anush008 1a68753
docs: Qdrant self-query retriever
Anush008 63b6b54
Merge branch 'main' into main
Anush008 4aba615
Merge pull request #1 from Anush008/self-query-qdrant-doc
Anush008 9caa60d
Update lock, fix type
jacoblee93 9f8da51
Fix deps
jacoblee93 e2f7aee
Merge branch 'main' of https://github.com/hwchase17/langchainjs into …
jacoblee93 ec689dd
Move to community
jacoblee93 f579d1a
Revert
jacoblee93 20e3aac
Move
jacoblee93 f5c24ad
Bump dep
jacoblee93 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import { | ||
QdrantVectorStore, | ||
QdrantFilter, | ||
QdrantCondition, | ||
} from "@langchain/community/vectorstores/qdrant"; | ||
|
||
import { | ||
Comparator, | ||
Comparators, | ||
Comparison, | ||
Operation, | ||
Operator, | ||
Operators, | ||
StructuredQuery, | ||
Visitor, | ||
} from "../../chains/query_constructor/ir.js"; | ||
import { BaseTranslator } from "./base.js"; | ||
import { isFilterEmpty, castValue, isInt, isFloat } from "./utils.js"; | ||
|
||
/** | ||
* 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?", | ||
* ); | ||
* ``` | ||
*/ | ||
export class QdrantTranslator< | ||
T extends QdrantVectorStore | ||
> extends BaseTranslator<T> { | ||
declare VisitOperationOutput: QdrantFilter; | ||
|
||
declare VisitComparisonOutput: QdrantCondition; | ||
|
||
allowedOperators: Operator[] = [Operators.and, Operators.or, Operators.not]; | ||
|
||
allowedComparators: Comparator[] = [ | ||
Comparators.eq, | ||
Comparators.ne, | ||
Comparators.lt, | ||
Comparators.lte, | ||
Comparators.gt, | ||
Comparators.gte, | ||
]; | ||
|
||
/** | ||
* Visits an operation and returns a QdrantFilter. | ||
* @param operation The operation to visit. | ||
* @returns A QdrantFilter. | ||
*/ | ||
visitOperation(operation: Operation): this["VisitOperationOutput"] { | ||
const args = operation.args?.map((arg) => arg.accept(this as Visitor)); | ||
|
||
const operator = { | ||
[Operators.and]: "must", | ||
[Operators.or]: "should", | ||
[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: Comparison): this["VisitComparisonOutput"] { | ||
const attribute = `metadata.${comparison.attribute}`; | ||
const value = castValue(comparison.value); | ||
|
||
if (comparison.comparator === "eq") { | ||
return { | ||
key: attribute, | ||
match: { | ||
value, | ||
}, | ||
}; | ||
} else if (comparison.comparator === "ne") { | ||
return { | ||
key: attribute, | ||
match: { | ||
except: [value], | ||
}, | ||
}; | ||
} | ||
|
||
if (!isInt(value) && !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: StructuredQuery | ||
): this["VisitStructuredQueryOutput"] { | ||
let nextArg = {}; | ||
if (query.filter) { | ||
nextArg = { | ||
filter: { must: [query.filter.accept(this as Visitor)] }, | ||
}; | ||
} | ||
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: QdrantFilter | undefined, | ||
generatedFilter: QdrantFilter | undefined, | ||
mergeType = "and", | ||
forceDefaultFilter = false | ||
): QdrantFilter | undefined { | ||
if (isFilterEmpty(defaultFilter) && isFilterEmpty(generatedFilter)) { | ||
return undefined; | ||
} | ||
if (isFilterEmpty(defaultFilter) || mergeType === "replace") { | ||
if (isFilterEmpty(generatedFilter)) { | ||
return undefined; | ||
} | ||
return generatedFilter; | ||
} | ||
if (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(): string { | ||
throw new Error("Not implemented"); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there! I noticed that this PR adds a new regular dependency, "@qdrant/js-client-rest", to the project. I've flagged this for your review to ensure it aligns with our dependency management strategy. Keep up the great work!