feat(Qdrant Vector Store Node): Qdrant vector store support (#8080)

## Summary
This PR intends to add [Qdrant](https://qdrant.tech/) as a supported
vectorstore node to load and retrieve documents from in a workflow.


## Review / Merge checklist
- [x] PR title and summary are descriptive. 
- [x] Node/credentials documentation to be updated in
https://github.com/n8n-io/n8n-docs/pull/1796.

---------

Co-authored-by: oleg <me@olegivaniv.com>
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Anush 2024-01-03 15:44:51 +05:30 committed by GitHub
parent ef3a57719e
commit 66460f66b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 228 additions and 2 deletions

View file

@ -0,0 +1,50 @@
import type {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class QdrantApi implements ICredentialType {
name = 'qdrantApi';
displayName = 'QdrantApi';
documentationUrl = 'https://docs.n8n.io/integrations/builtin/credentials/qdrant/';
properties: INodeProperties[] = [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
typeOptions: { password: true },
required: true,
default: '',
},
{
displayName: 'Qdrant URL',
name: 'qdrantUrl',
type: 'string',
required: true,
default: '',
},
];
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
'api-key': '={{$credentials.apiKey}}',
},
},
};
test: ICredentialTestRequest = {
request: {
baseURL: '={{$credentials.qdrantUrl}}',
headers: {
accept: 'application/json; charset=utf-8',
},
},
};
}

View file

@ -0,0 +1,85 @@
import { type INodeProperties } from 'n8n-workflow';
import type { QdrantLibArgs } from 'langchain/vectorstores/qdrant';
import { QdrantVectorStore } from 'langchain/vectorstores/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';
const sharedFields: INodeProperties[] = [qdrantCollectionRLC];
const insertFields: INodeProperties[] = [
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Collection Config',
name: 'collectionConfig',
type: 'json',
default: '',
description:
'JSON options for creating a collection. <a href="https://qdrant.tech/documentation/concepts/collections">Learn more</a>.',
},
],
},
];
export const VectorStoreQdrant = createVectorStoreNode({
meta: {
displayName: 'Qdrant Vector Store',
name: 'vectorStoreQdrant',
description: 'Work with your data in a Qdrant collection',
icon: 'file:qdrant.svg',
docsUrl:
'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.vectorstoreqdrant/',
credentials: [
{
name: 'qdrantApi',
required: true,
},
],
},
methods: { listSearch: { qdrantCollectionsSearch } },
insertFields,
sharedFields,
async getVectorStoreClient(context, filter, embeddings, itemIndex) {
const collection = context.getNodeParameter('qdrantCollection', itemIndex, '', {
extractValue: true,
}) as string;
const credentials = await context.getCredentials('qdrantApi');
const config: QdrantLibArgs = {
url: credentials.qdrantUrl as string,
apiKey: credentials.apiKey as string,
collectionName: collection,
};
return QdrantVectorStore.fromExistingCollection(embeddings, config);
},
async populateVectorStore(context, embeddings, documents, itemIndex) {
const collectionName = context.getNodeParameter('qdrantCollection', itemIndex, '', {
extractValue: true,
}) as string;
// If collection config is not provided, the collection will be created with default settings
// i.e. with the size of the passed embeddings and "Cosine" distance metric
const { collectionConfig } = context.getNodeParameter('options', itemIndex, {}) as {
collectionConfig?: QdrantSchemas['CreateCollection'];
};
const credentials = await context.getCredentials('qdrantApi');
const config: QdrantLibArgs = {
url: credentials.qdrantUrl as string,
apiKey: credentials.apiKey as string,
collectionName,
collectionConfig,
};
await QdrantVectorStore.fromDocuments(documents, embeddings, config);
},
});

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256px" height="296px" viewBox="0 0 256 296" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<title>qdrant</title>
<defs>
<linearGradient x1="81.5619048%" y1="44.8421053%" x2="-18.0857143%" y2="44.8421053%" id="linearGradient-1">
<stop stop-color="#FF3364" offset="0%"></stop>
<stop stop-color="#C91540" stop-opacity="0" offset="100%"></stop>
</linearGradient>
</defs>
<g>
<polygon fill="#24386C" points="201.31705 271.722427 195.422715 109.21267 184.747781 66.3682368 256 73.9112545 256 270.492509 212.474757 295.612626"></polygon>
<polygon fill="#7589BE" points="255.995151 73.8998107 212.469908 99.037384 122.649634 79.3346471 17.5160008 122.140288 1.1370484e-14 73.8998107 63.9883137 36.9499053 127.996024 0 191.986277 36.9499053"></polygon>
<polyline fill="#B2BFE8" points="0.00310340412 73.8998107 43.5283462 99.037384 68.7590217 174.073816 153.949405 242.236209 128.001067 295.599243 63.9933568 258.647398 0.00310340412 221.697492 0.00310340412 73.897871"></polyline>
<polyline fill="#24386C" points="156.856906 202.807459 128.001067 245.347371 128.001067 295.603122 168.946605 271.978458 190.043934 240.475027"></polyline>
<polygon fill="#7589BE" points="128.018523 195.107138 87.0555287 124.184656 95.8787005 100.678309 129.42068 84.4156955 168.946411 124.185819"></polygon>
<polyline fill="#B2BFE8" points="87.0555287 124.178837 128.001067 147.803501 128.001067 195.091621 90.131778 196.720927 67.2247763 167.471344 87.0555287 124.178856"></polyline>
<polygon fill="#24386C" points="128.001067 147.799621 168.946605 124.176897 196.813234 170.576668 163.090869 198.439418 128.001067 195.089293"></polygon>
<path d="M168.946605,271.974579 L212.471848,295.601182 L212.471848,99.0393237 L170.226759,74.658205 L128.001067,50.2770864 L85.7559782,74.658205 L43.5302858,99.0393237 L43.5302858,196.581255 L85.7559782,220.962373 L128.001067,245.345432 L168.946605,221.699432 L168.946605,271.974579 Z M168.946605,171.443681 L128.001067,195.087742 L87.0555287,171.443681 L87.0555287,124.174957 L128.001067,100.530897 L168.946605,124.174957 L168.946605,171.443681" fill="#DC244C"></path>
<polygon fill="url(#linearGradient-1)" points="128.018523 245.362888 128.018523 195.099379 87.2863443 171.657041 87.2863443 221.837146"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -45,3 +45,26 @@ export const supabaseTableNameRLC: INodeProperties = {
}, },
], ],
}; };
export const qdrantCollectionRLC: INodeProperties = {
displayName: 'Qdrant Collection',
name: 'qdrantCollection',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
required: true,
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'qdrantCollectionsSearch',
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
},
],
};

View file

@ -1,5 +1,6 @@
import { ApplicationError, type IDataObject, type ILoadOptionsFunctions } from 'n8n-workflow'; import { ApplicationError, type IDataObject, type ILoadOptionsFunctions } from 'n8n-workflow';
import { Pinecone } from '@pinecone-database/pinecone'; import { Pinecone } from '@pinecone-database/pinecone';
import { QdrantClient } from '@qdrant/js-client-rest';
export async function pineconeIndexSearch(this: ILoadOptionsFunctions) { export async function pineconeIndexSearch(this: ILoadOptionsFunctions) {
const credentials = await this.getCredentials('pineconeApi'); const credentials = await this.getCredentials('pineconeApi');
@ -49,3 +50,21 @@ export async function supabaseTableNameSearch(this: ILoadOptionsFunctions) {
return { results }; return { results };
} }
export async function qdrantCollectionsSearch(this: ILoadOptionsFunctions) {
const credentials = await this.getCredentials('qdrantApi');
const client = new QdrantClient({
url: credentials.qdrantUrl as string,
apiKey: credentials.apiKey as string,
});
const response = await client.getCollections();
const results = response.collections.map((collection) => ({
name: collection.name,
value: collection.name,
}));
return { results };
}

View file

@ -33,6 +33,7 @@
"dist/credentials/MotorheadApi.credentials.js", "dist/credentials/MotorheadApi.credentials.js",
"dist/credentials/OllamaApi.credentials.js", "dist/credentials/OllamaApi.credentials.js",
"dist/credentials/PineconeApi.credentials.js", "dist/credentials/PineconeApi.credentials.js",
"dist/credentials/QdrantApi.credentials.js",
"dist/credentials/SerpApi.credentials.js", "dist/credentials/SerpApi.credentials.js",
"dist/credentials/WolframAlphaApi.credentials.js", "dist/credentials/WolframAlphaApi.credentials.js",
"dist/credentials/XataApi.credentials.js", "dist/credentials/XataApi.credentials.js",
@ -93,6 +94,7 @@
"dist/nodes/vector_store/VectorStorePinecone/VectorStorePinecone.node.js", "dist/nodes/vector_store/VectorStorePinecone/VectorStorePinecone.node.js",
"dist/nodes/vector_store/VectorStorePineconeInsert/VectorStorePineconeInsert.node.js", "dist/nodes/vector_store/VectorStorePineconeInsert/VectorStorePineconeInsert.node.js",
"dist/nodes/vector_store/VectorStorePineconeLoad/VectorStorePineconeLoad.node.js", "dist/nodes/vector_store/VectorStorePineconeLoad/VectorStorePineconeLoad.node.js",
"dist/nodes/vector_store/VectorStoreQdrant/VectorStoreQdrant.node.js",
"dist/nodes/vector_store/VectorStoreSupabase/VectorStoreSupabase.node.js", "dist/nodes/vector_store/VectorStoreSupabase/VectorStoreSupabase.node.js",
"dist/nodes/vector_store/VectorStoreSupabaseInsert/VectorStoreSupabaseInsert.node.js", "dist/nodes/vector_store/VectorStoreSupabaseInsert/VectorStoreSupabaseInsert.node.js",
"dist/nodes/vector_store/VectorStoreSupabaseLoad/VectorStoreSupabaseLoad.node.js", "dist/nodes/vector_store/VectorStoreSupabaseLoad/VectorStoreSupabaseLoad.node.js",
@ -118,6 +120,7 @@
"@huggingface/inference": "2.6.4", "@huggingface/inference": "2.6.4",
"@n8n/vm2": "3.9.20", "@n8n/vm2": "3.9.20",
"@pinecone-database/pinecone": "1.1.2", "@pinecone-database/pinecone": "1.1.2",
"@qdrant/js-client-rest": "1.7.0",
"@supabase/supabase-js": "2.38.5", "@supabase/supabase-js": "2.38.5",
"@xata.io/client": "0.25.3", "@xata.io/client": "0.25.3",
"cohere-ai": "6.2.2", "cohere-ai": "6.2.2",

View file

@ -192,6 +192,9 @@ importers:
'@pinecone-database/pinecone': '@pinecone-database/pinecone':
specifier: 1.1.2 specifier: 1.1.2
version: 1.1.2 version: 1.1.2
'@qdrant/js-client-rest':
specifier: 1.7.0
version: 1.7.0(typescript@5.3.2)
'@supabase/supabase-js': '@supabase/supabase-js':
specifier: 2.38.5 specifier: 2.38.5
version: 2.38.5 version: 2.38.5
@ -215,7 +218,7 @@ importers:
version: 1.2.0 version: 1.2.0
langchain: langchain:
specifier: 0.0.198 specifier: 0.0.198
version: 0.0.198(@aws-sdk/client-bedrock-runtime@3.454.0)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@0.2.1)(@huggingface/inference@2.6.4)(@pinecone-database/pinecone@1.1.2)(@supabase/supabase-js@2.38.5)(@xata.io/client@0.25.3)(cohere-ai@6.2.2)(d3-dsv@2.0.0)(epub2@3.0.1)(html-to-text@9.0.5)(lodash@4.17.21)(mammoth@1.6.0)(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.11)(typeorm@0.3.17) version: 0.0.198(@aws-sdk/client-bedrock-runtime@3.454.0)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@0.2.1)(@huggingface/inference@2.6.4)(@pinecone-database/pinecone@1.1.2)(@qdrant/js-client-rest@1.7.0)(@supabase/supabase-js@2.38.5)(@xata.io/client@0.25.3)(cohere-ai@6.2.2)(d3-dsv@2.0.0)(epub2@3.0.1)(html-to-text@9.0.5)(lodash@4.17.21)(mammoth@1.6.0)(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.11)(typeorm@0.3.17)
lodash: lodash:
specifier: 4.17.21 specifier: 4.17.21
version: 4.17.21 version: 4.17.21
@ -6544,6 +6547,23 @@ packages:
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
dev: false dev: false
/@qdrant/js-client-rest@1.7.0(typescript@5.3.2):
resolution: {integrity: sha512-16O0EQfrrybcPVipodxykr6dMUlBzKW7a63cSDUFVgc5a1AWESwERykwjuvW5KqvKdkPcxZ2NssrvgUO1W3MgA==}
engines: {node: '>=18.0.0', pnpm: '>=8'}
peerDependencies:
typescript: ^5.3.0
dependencies:
'@qdrant/openapi-typescript-fetch': 1.2.1
'@sevinf/maybe': 0.5.0
typescript: 5.3.2
undici: 5.26.4
dev: false
/@qdrant/openapi-typescript-fetch@1.2.1:
resolution: {integrity: sha512-oiBJRN1ME7orFZocgE25jrM3knIF/OKJfMsZPBbtMMKfgNVYfps0MokGvSJkBmecj6bf8QoLXWIGlIoaTM4Zmw==}
engines: {node: '>=12.0.0', pnpm: '>=8'}
dev: false
/@radix-ui/number@1.0.1: /@radix-ui/number@1.0.1:
resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==}
dependencies: dependencies:
@ -7391,6 +7411,10 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@sevinf/maybe@0.5.0:
resolution: {integrity: sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg==}
dev: false
/@sideway/address@4.1.4: /@sideway/address@4.1.4:
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
dependencies: dependencies:
@ -18585,7 +18609,7 @@ packages:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
dev: false dev: false
/langchain@0.0.198(@aws-sdk/client-bedrock-runtime@3.454.0)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@0.2.1)(@huggingface/inference@2.6.4)(@pinecone-database/pinecone@1.1.2)(@supabase/supabase-js@2.38.5)(@xata.io/client@0.25.3)(cohere-ai@6.2.2)(d3-dsv@2.0.0)(epub2@3.0.1)(html-to-text@9.0.5)(lodash@4.17.21)(mammoth@1.6.0)(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.11)(typeorm@0.3.17): /langchain@0.0.198(@aws-sdk/client-bedrock-runtime@3.454.0)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@0.2.1)(@huggingface/inference@2.6.4)(@pinecone-database/pinecone@1.1.2)(@qdrant/js-client-rest@1.7.0)(@supabase/supabase-js@2.38.5)(@xata.io/client@0.25.3)(cohere-ai@6.2.2)(d3-dsv@2.0.0)(epub2@3.0.1)(html-to-text@9.0.5)(lodash@4.17.21)(mammoth@1.6.0)(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.11)(typeorm@0.3.17):
resolution: {integrity: sha512-YC0O1g8r61InCWyF5NmiQjdghdq6LKcgMrDZtqLbgDxAe4RoSldonm+5oNXS3yjCISG0j3s5Cty+yB7klqvUpg==} resolution: {integrity: sha512-YC0O1g8r61InCWyF5NmiQjdghdq6LKcgMrDZtqLbgDxAe4RoSldonm+5oNXS3yjCISG0j3s5Cty+yB7klqvUpg==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
@ -18898,6 +18922,7 @@ packages:
'@huggingface/inference': 2.6.4 '@huggingface/inference': 2.6.4
'@langchain/core': 0.0.2 '@langchain/core': 0.0.2
'@pinecone-database/pinecone': 1.1.2 '@pinecone-database/pinecone': 1.1.2
'@qdrant/js-client-rest': 1.7.0(typescript@5.3.2)
'@supabase/supabase-js': 2.38.5 '@supabase/supabase-js': 2.38.5
'@xata.io/client': 0.25.3(typescript@5.3.2) '@xata.io/client': 0.25.3(typescript@5.3.2)
binary-extensions: 2.2.0 binary-extensions: 2.2.0