Skip to content

Commit

Permalink
feat: Qdrant Vector Store search filter (n8n-io#9900)
Browse files Browse the repository at this point in the history
Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: Oleg Ivaniv <me@olegivaniv.com>
  • Loading branch information
michael-radency and OlegIvaniv authored Jul 4, 2024
1 parent 058fa32 commit fbe4bca
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import { type INodeProperties } from 'n8n-workflow';
import type { QdrantLibArgs } from '@langchain/community/vectorstores/qdrant';
import { QdrantVectorStore } from '@langchain/community/vectorstores/qdrant';
import type { IDataObject, INodeProperties } from 'n8n-workflow';
import type { QdrantLibArgs } from '@langchain/qdrant';
import { QdrantVectorStore } from '@langchain/qdrant';
import type { Schemas as QdrantSchemas } from '@qdrant/js-client-rest';
import { createVectorStoreNode } from '../shared/createVectorStoreNode';
import { qdrantCollectionRLC } from '../shared/descriptions';
import { qdrantCollectionsSearch } from '../shared/methods/listSearch';
import type { Embeddings } from '@langchain/core/embeddings';
import type { Callbacks } from '@langchain/core/callbacks/manager';

class ExtendedQdrantVectorStore extends QdrantVectorStore {
private static defaultFilter: IDataObject = {};

static async fromExistingCollection(
embeddings: Embeddings,
args: QdrantLibArgs,
defaultFilter: IDataObject = {},
): Promise<QdrantVectorStore> {
ExtendedQdrantVectorStore.defaultFilter = defaultFilter;
return await super.fromExistingCollection(embeddings, args);
}

async similaritySearch(
query: string,
k: number,
filter?: IDataObject,
callbacks?: Callbacks | undefined,
) {
const mergedFilter = { ...ExtendedQdrantVectorStore.defaultFilter, ...filter };
return await super.similaritySearch(query, k, mergedFilter, callbacks);
}
}

const sharedFields: INodeProperties[] = [qdrantCollectionRLC];

Expand All @@ -28,6 +53,31 @@ const insertFields: INodeProperties[] = [
},
];

const retrieveFields: INodeProperties[] = [
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Search Filter',
name: 'searchFilterJson',
type: 'json',
typeOptions: {
rows: 5,
},
default:
'{\n "should": [\n {\n "key": "metadata.batch",\n "match": {\n "value": 12345\n }\n }\n ]\n}',
validateType: 'object',
description:
'Filter pageContent or metadata using this <a href="https://qdrant.tech/documentation/concepts/filtering/" target="_blank">filtering syntax</a>',
},
],
},
];

export const VectorStoreQdrant = createVectorStoreNode({
meta: {
displayName: 'Qdrant Vector Store',
Expand All @@ -44,9 +94,11 @@ export const VectorStoreQdrant = createVectorStoreNode({
],
},
methods: { listSearch: { qdrantCollectionsSearch } },
loadFields: retrieveFields,
insertFields,
sharedFields,
async getVectorStoreClient(context, _, embeddings, itemIndex) {
retrieveFields,
async getVectorStoreClient(context, filter, embeddings, itemIndex) {
const collection = context.getNodeParameter('qdrantCollection', itemIndex, '', {
extractValue: true,
}) as string;
Expand All @@ -59,7 +111,7 @@ export const VectorStoreQdrant = createVectorStoreNode({
collectionName: collection,
};

return await QdrantVectorStore.fromExistingCollection(embeddings, config);
return await ExtendedQdrantVectorStore.fromExistingCollection(embeddings, config, filter);
},
async populateVectorStore(context, embeddings, documents, itemIndex) {
const collectionName = context.getNodeParameter('qdrantCollection', itemIndex, '', {
Expand Down
3 changes: 2 additions & 1 deletion packages/@n8n/nodes-langchain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "3.535.0",
"@aws-sdk/credential-provider-node": "3.535.0",
"@getzep/zep-js": "0.9.0",
"@getzep/zep-cloud": "1.0.6",
"@getzep/zep-js": "0.9.0",
"@google-ai/generativelanguage": "2.5.0",
"@google/generative-ai": "0.11.4",
"@huggingface/inference": "2.7.0",
Expand All @@ -148,6 +148,7 @@
"@langchain/mistralai": "0.0.22",
"@langchain/openai": "0.0.33",
"@langchain/pinecone": "0.0.6",
"@langchain/qdrant": "^0.0.5",
"@langchain/redis": "0.0.5",
"@langchain/textsplitters": "0.0.2",
"@mozilla/readability": "^0.5.0",
Expand Down
24 changes: 18 additions & 6 deletions packages/@n8n/nodes-langchain/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,24 @@ export function getMetadataFiltersValues(
ctx: IExecuteFunctions,
itemIndex: number,
): Record<string, never> | undefined {
const metadata = ctx.getNodeParameter('options.metadata.metadataValues', itemIndex, []) as Array<{
name: string;
value: string;
}>;
if (metadata.length > 0) {
return metadata.reduce((acc, { name, value }) => ({ ...acc, [name]: value }), {});
const options = ctx.getNodeParameter('options', itemIndex);

if (options.metadata) {
const { metadataValues: metadata } = options.metadata as {
metadataValues: Array<{
name: string;
value: string;
}>;
};
if (metadata.length > 0) {
return metadata.reduce((acc, { name, value }) => ({ ...acc, [name]: value }), {});
}
}

if (options.searchFilterJson) {
return ctx.getNodeParameter('options.searchFilterJson', itemIndex, '', {
ensureType: 'object',
}) as Record<string, never>;
}

return undefined;
Expand Down
45 changes: 37 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fbe4bca

Please sign in to comment.