From cf0b24af0275bf8cb821be15462976c9149fe024 Mon Sep 17 00:00:00 2001 From: Timothy Carambat <rambat1010@gmail.com> Date: Tue, 15 Aug 2023 15:26:44 -0700 Subject: [PATCH] Add Qdrant support for embedding, chat, and conversation (#192) * Add Qdrant support for embedding, chat, and conversation * Change comments --- .vscode/settings.json | 1 + docker/.env.example | 5 + .../Modals/Settings/VectorDbs/index.jsx | 45 ++ frontend/src/media/vectordbs/qdrant.png | Bin 0 -> 15073 bytes server/.env.example | 5 + server/endpoints/system.js | 6 + server/package.json | 1 + server/utils/helpers/index.js | 3 + server/utils/helpers/updateENV.js | 10 +- .../utils/vectorDbProviders/qdrant/index.js | 397 ++++++++++++++++++ server/yarn.lock | 28 +- 11 files changed, 499 insertions(+), 2 deletions(-) create mode 100644 frontend/src/media/vectordbs/qdrant.png create mode 100644 server/utils/vectorDbProviders/qdrant/index.js diff --git a/.vscode/settings.json b/.vscode/settings.json index c8c7ea995..dde2d134b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "openai", + "Qdrant", "Weaviate" ] } \ No newline at end of file diff --git a/docker/.env.example b/docker/.env.example index 77550b6f0..70c61ef96 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -37,6 +37,11 @@ PINECONE_INDEX= # WEAVIATE_ENDPOINT="http://localhost:8080" # WEAVIATE_API_KEY= +# Enable all below if you are using vector database: Qdrant. +# VECTOR_DB="qdrant" +# QDRANT_ENDPOINT="http://localhost:6333" +# QDRANT_API_KEY= + # CLOUD DEPLOYMENT VARIRABLES ONLY # AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting. # NO_DEBUG="true" diff --git a/frontend/src/components/Modals/Settings/VectorDbs/index.jsx b/frontend/src/components/Modals/Settings/VectorDbs/index.jsx index b1a5a97b5..ec25e0a7e 100644 --- a/frontend/src/components/Modals/Settings/VectorDbs/index.jsx +++ b/frontend/src/components/Modals/Settings/VectorDbs/index.jsx @@ -4,6 +4,7 @@ import ChromaLogo from "../../../../media/vectordbs/chroma.png"; import PineconeLogo from "../../../../media/vectordbs/pinecone.png"; import LanceDbLogo from "../../../../media/vectordbs/lancedb.png"; import WeaviateLogo from "../../../../media/vectordbs/weaviate.png"; +import QDrantLogo from "../../../../media/vectordbs/qdrant.png"; const noop = () => false; export default function VectorDBSelection({ @@ -80,6 +81,15 @@ export default function VectorDBSelection({ image={PineconeLogo} onClick={updateVectorChoice} /> + <VectorDBOption + name="QDrant" + value="qdrant" + link="qdrant.tech" + description="Open source local and distributed cloud vector database." + checked={vectorDB === "qdrant"} + image={QDrantLogo} + onClick={updateVectorChoice} + /> <VectorDBOption name="Weaviate" value="weaviate" @@ -181,6 +191,41 @@ export default function VectorDBSelection({ </p> </div> )} + {vectorDB === "qdrant" && ( + <> + <div> + <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"> + QDrant API Endpoint + </label> + <input + type="url" + name="QdrantEndpoint" + disabled={!canDebug} + className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200" + placeholder="http://localhost:6633" + defaultValue={settings?.QdrantEndpoint} + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> + <div> + <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"> + Api Key + </label> + <input + type="password" + name="QdrantApiKey" + disabled={!canDebug} + className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200" + placeholder="wOeqxsYP4....1244sba" + defaultValue={settings?.QdrantApiKey} + autoComplete="off" + spellCheck={false} + /> + </div> + </> + )} {vectorDB === "weaviate" && ( <> <div> diff --git a/frontend/src/media/vectordbs/qdrant.png b/frontend/src/media/vectordbs/qdrant.png new file mode 100644 index 0000000000000000000000000000000000000000..d63e720c5402f3abd7246ffdd0ad6f853063c2a5 GIT binary patch literal 15073 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 SDRKYysH~)tM1`19;C}<3s#MGX literal 0 HcmV?d00001 diff --git a/server/.env.example b/server/.env.example index 606dd8988..ff92295ec 100644 --- a/server/.env.example +++ b/server/.env.example @@ -36,6 +36,11 @@ PINECONE_INDEX= # WEAVIATE_ENDPOINT="http://localhost:8080" # WEAVIATE_API_KEY= +# Enable all below if you are using vector database: Qdrant. +# VECTOR_DB="qdrant" +# QDRANT_ENDPOINT="http://localhost:6333" +# QDRANT_API_KEY= + # CLOUD DEPLOYMENT VARIRABLES ONLY # AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting. diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 73041e183..d0b48cc62 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -78,6 +78,12 @@ function systemEndpoints(app) { WeaviateApiKey: process.env.WEAVIATE_API_KEY, } : {}), + ...(vectorDB === "qdrant" + ? { + QdrantEndpoint: process.env.QDRANT_ENDPOINT, + QdrantApiKey: process.env.QDRANT_API_KEY, + } + : {}), LLMProvider: llmProvider, ...(llmProvider === "openai" ? { diff --git a/server/package.json b/server/package.json index b2f5bdc8a..15bbff6f6 100644 --- a/server/package.json +++ b/server/package.json @@ -18,6 +18,7 @@ "@azure/openai": "^1.0.0-beta.3", "@googleapis/youtube": "^9.0.0", "@pinecone-database/pinecone": "^0.1.6", + "@qdrant/js-client-rest": "^1.4.0", "archiver": "^5.3.1", "bcrypt": "^5.1.0", "body-parser": "^1.20.2", diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index b7fb5ae00..b077606ad 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -13,6 +13,9 @@ function getVectorDbClass() { case "weaviate": const { Weaviate } = require("../vectorDbProviders/weaviate"); return Weaviate; + case "qdrant": + const { QDrant } = require("../vectorDbProviders/qdrant"); + return QDrant; default: throw new Error("ENV: No VECTOR_DB value found in environment!"); } diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 9f00ec423..d08f25c7a 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -47,6 +47,14 @@ const KEY_MAPPING = { envKey: "WEAVIATE_API_KEY", checks: [], }, + QdrantEndpoint: { + envKey: "QDRANT_ENDPOINT", + checks: [isValidURL], + }, + QdrantApiKey: { + envKey: "QDRANT_API_KEY", + checks: [], + }, PineConeEnvironment: { envKey: "PINECONE_ENVIRONMENT", @@ -112,7 +120,7 @@ function validOpenAIModel(input = "") { } function supportedVectorDB(input = "") { - const supported = ["chroma", "pinecone", "lancedb", "weaviate"]; + const supported = ["chroma", "pinecone", "lancedb", "weaviate", "qdrant"]; return supported.includes(input) ? null : `Invalid VectorDB type. Must be one of ${supported.join(", ")}.`; diff --git a/server/utils/vectorDbProviders/qdrant/index.js b/server/utils/vectorDbProviders/qdrant/index.js new file mode 100644 index 000000000..0dc39e790 --- /dev/null +++ b/server/utils/vectorDbProviders/qdrant/index.js @@ -0,0 +1,397 @@ +const { QdrantClient } = require("@qdrant/js-client-rest"); +const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter"); +const { storeVectorResult, cachedVectorInformation } = require("../../files"); +const { v4: uuidv4 } = require("uuid"); +const { toChunks, getLLMProvider } = require("../../helpers"); +const { chatPrompt } = require("../../chats"); + +const QDrant = { + name: "QDrant", + connect: async function () { + if (process.env.VECTOR_DB !== "qdrant") + throw new Error("QDrant::Invalid ENV settings"); + + const client = new QdrantClient({ + url: process.env.QDRANT_ENDPOINT, + ...(process.env.QDRANT_API_KEY + ? { apiKey: process.env.QDRANT_API_KEY } + : {}), + }); + + const isAlive = (await client.api("cluster")?.clusterStatus())?.ok || false; + if (!isAlive) + throw new Error( + "QDrant::Invalid Heartbeat received - is the instance online?" + ); + + return { client }; + }, + heartbeat: async function () { + await this.connect(); + return { heartbeat: Number(new Date()) }; + }, + totalIndicies: async function () { + const { client } = await this.connect(); + const { collections } = await client.getCollections(); + var totalVectors = 0; + for (const collection of collections) { + if (!collection || !collection.name) continue; + totalVectors += + (await this.namespace(client, collection.name))?.vectorCount || 0; + } + return totalVectors; + }, + namespaceCount: async function (_namespace = null) { + const { client } = await this.connect(); + const namespace = await this.namespace(client, _namespace); + return namespace?.vectorCount || 0; + }, + similarityResponse: async function (_client, namespace, queryVector) { + const { client } = await this.connect(); + const result = { + contextTexts: [], + sourceDocuments: [], + }; + + const responses = await client.search(namespace, { + vector: queryVector, + limit: 4, + }); + + responses.forEach((response) => { + result.contextTexts.push(response?.payload?.text || ""); + result.sourceDocuments.push({ + ...(response?.payload || {}), + id: response.id, + }); + }); + + return result; + }, + namespace: async function (client, namespace = null) { + if (!namespace) throw new Error("No namespace value provided."); + const collection = await client.getCollection(namespace).catch(() => null); + if (!collection) return null; + + return { + name: namespace, + ...collection, + vectorCount: collection.vectors_count, + }; + }, + hasNamespace: async function (namespace = null) { + if (!namespace) return false; + const { client } = await this.connect(); + return await this.namespaceExists(client, namespace); + }, + namespaceExists: async function (client, namespace = null) { + if (!namespace) throw new Error("No namespace value provided."); + const collection = await client.getCollection(namespace).catch((e) => { + console.error("QDrant::namespaceExists", e.message); + return null; + }); + return !!collection; + }, + deleteVectorsInNamespace: async function (client, namespace = null) { + await client.deleteCollection(namespace); + return true; + }, + getOrCreateCollection: async function (client, namespace) { + if (await this.namespaceExists(client, namespace)) { + return await client.getCollection(namespace); + } + await client.createCollection(namespace, { + vectors: { + size: 1536, //TODO: Fixed to OpenAI models - when other embeddings exist make variable. + distance: "Cosine", + }, + }); + return await client.getCollection(namespace); + }, + addDocumentToNamespace: async function ( + namespace, + documentData = {}, + fullFilePath = null + ) { + const { DocumentVectors } = require("../../../models/vectors"); + try { + const { pageContent, docId, ...metadata } = documentData; + if (!pageContent || pageContent.length == 0) return false; + + console.log("Adding new vectorized document into namespace", namespace); + const cacheResult = await cachedVectorInformation(fullFilePath); + if (cacheResult.exists) { + const { client } = await this.connect(); + const collection = await this.getOrCreateCollection(client, namespace); + if (!collection) + throw new Error("Failed to create new QDrant collection!", { + namespace, + }); + + const { chunks } = cacheResult; + const documentVectors = []; + + for (const chunk of chunks) { + const submission = { + ids: [], + vectors: [], + payloads: [], + }; + + // Before sending to Qdrant and saving the records to our db + // we need to assign the id of each chunk that is stored in the cached file. + chunk.forEach((chunk) => { + const id = uuidv4(); + const { id: _id, ...payload } = chunk.payload; + documentVectors.push({ docId, vectorId: id }); + submission.ids.push(id); + submission.vectors.push(chunk.vector); + submission.payloads.push(payload); + }); + + const additionResult = await client.upsert(namespace, { + wait: true, + batch: { ...submission }, + }); + if (additionResult?.status !== "completed") + throw new Error("Error embedding into QDrant", additionResult); + } + + await DocumentVectors.bulkInsert(documentVectors); + return true; + } + + // If we are here then we are going to embed and store a novel document. + // We have to do this manually as opposed to using LangChains `Qdrant.fromDocuments` + // because we then cannot atomically control our namespace to granularly find/remove documents + // from vectordb. + const textSplitter = new RecursiveCharacterTextSplitter({ + chunkSize: 1000, + chunkOverlap: 20, + }); + const textChunks = await textSplitter.splitText(pageContent); + + console.log("Chunks created from document:", textChunks.length); + const LLMConnector = getLLMProvider(); + const documentVectors = []; + const vectors = []; + const vectorValues = await LLMConnector.embedChunks(textChunks); + const submission = { + ids: [], + vectors: [], + payloads: [], + }; + + if (!!vectorValues && vectorValues.length > 0) { + for (const [i, vector] of vectorValues.entries()) { + const vectorRecord = { + id: uuidv4(), + vector: vector, + // [DO NOT REMOVE] + // LangChain will be unable to find your text if you embed manually and dont include the `text` key. + // https://github.com/hwchase17/langchainjs/blob/2def486af734c0ca87285a48f1a04c057ab74bdf/langchain/src/vectorstores/pinecone.ts#L64 + payload: { ...metadata, text: textChunks[i] }, + }; + + submission.ids.push(vectorRecord.id); + submission.vectors.push(vectorRecord.vector); + submission.payloads.push(vectorRecord.payload); + + vectors.push(vectorRecord); + documentVectors.push({ docId, vectorId: vectorRecord.id }); + } + } else { + console.error( + "Could not use OpenAI to embed document chunks! This document will not be recorded." + ); + } + + const { client } = await this.connect(); + const collection = await this.getOrCreateCollection(client, namespace); + if (!collection) + throw new Error("Failed to create new QDrant collection!", { + namespace, + }); + + if (vectors.length > 0) { + const chunks = []; + + console.log("Inserting vectorized chunks into QDrant collection."); + for (const chunk of toChunks(vectors, 500)) chunks.push(chunk); + + const additionResult = await client.upsert(namespace, { + wait: true, + batch: { + ids: submission.ids, + vectors: submission.vectors, + payloads: submission.payloads, + }, + }); + if (additionResult?.status !== "completed") + throw new Error("Error embedding into QDrant", additionResult); + + await storeVectorResult(chunks, fullFilePath); + } + + await DocumentVectors.bulkInsert(documentVectors); + return true; + } catch (e) { + console.error("addDocumentToNamespace", e.message); + return false; + } + }, + deleteDocumentFromNamespace: async function (namespace, docId) { + const { DocumentVectors } = require("../../../models/vectors"); + const { client } = await this.connect(); + if (!(await this.namespaceExists(client, namespace))) return; + + const knownDocuments = await DocumentVectors.where(`docId = '${docId}'`); + if (knownDocuments.length === 0) return; + + const vectorIds = knownDocuments.map((doc) => doc.vectorId); + await client.delete(namespace, { + wait: true, + points: vectorIds, + }); + + const indexes = knownDocuments.map((doc) => doc.id); + await DocumentVectors.deleteIds(indexes); + return true; + }, + query: async function (reqBody = {}) { + const { namespace = null, input, workspace = {} } = reqBody; + if (!namespace || !input) throw new Error("Invalid request body"); + + const { client } = await this.connect(); + if (!(await this.namespaceExists(client, namespace))) { + return { + response: null, + sources: [], + message: "Invalid query - no documents found for workspace!", + }; + } + + const LLMConnector = getLLMProvider(); + const queryVector = await LLMConnector.embedTextInput(input); + const { contextTexts, sourceDocuments } = await this.similarityResponse( + client, + namespace, + queryVector + ); + const prompt = { + role: "system", + content: `${chatPrompt(workspace)} + Context: + ${contextTexts + .map((text, i) => { + return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`; + }) + .join("")}`, + }; + const memory = [prompt, { role: "user", content: input }]; + const responseText = await LLMConnector.getChatCompletion(memory, { + temperature: workspace?.openAiTemp ?? 0.7, + }); + + return { + response: responseText, + sources: this.curateSources(sourceDocuments), + message: false, + }; + }, + // This implementation of chat uses the chat history and modifies the system prompt at execution + // this is improved over the regular langchain implementation so that chats do not directly modify embeddings + // because then multi-user support will have all conversations mutating the base vector collection to which then + // the only solution is replicating entire vector databases per user - which will very quickly consume space on VectorDbs + chat: async function (reqBody = {}) { + const { + namespace = null, + input, + workspace = {}, + chatHistory = [], + } = reqBody; + if (!namespace || !input) throw new Error("Invalid request body"); + + const { client } = await this.connect(); + if (!(await this.namespaceExists(client, namespace))) { + return { + response: null, + sources: [], + message: "Invalid query - no documents found for workspace!", + }; + } + + const LLMConnector = getLLMProvider(); + const queryVector = await LLMConnector.embedTextInput(input); + const { contextTexts, sourceDocuments } = await this.similarityResponse( + client, + namespace, + queryVector + ); + const prompt = { + role: "system", + content: `${chatPrompt(workspace)} + Context: + ${contextTexts + .map((text, i) => { + return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`; + }) + .join("")}`, + }; + const memory = [prompt, ...chatHistory, { role: "user", content: input }]; + const responseText = await LLMConnector.getChatCompletion(memory, { + temperature: workspace?.openAiTemp ?? 0.7, + }); + + return { + response: responseText, + sources: this.curateSources(sourceDocuments), + message: false, + }; + }, + "namespace-stats": async function (reqBody = {}) { + const { namespace = null } = reqBody; + if (!namespace) throw new Error("namespace required"); + const { client } = await this.connect(); + if (!(await this.namespaceExists(client, namespace))) + throw new Error("Namespace by that name does not exist."); + const stats = await this.namespace(client, namespace); + return stats + ? stats + : { message: "No stats were able to be fetched from DB for namespace" }; + }, + "delete-namespace": async function (reqBody = {}) { + const { namespace = null } = reqBody; + const { client } = await this.connect(); + if (!(await this.namespaceExists(client, namespace))) + throw new Error("Namespace by that name does not exist."); + + const details = await this.namespace(client, namespace); + await this.deleteVectorsInNamespace(client, namespace); + return { + message: `Namespace ${namespace} was deleted along with ${details?.vectorCount} vectors.`, + }; + }, + reset: async function () { + const { client } = await this.connect(); + const response = await client.getCollections(); + for (const collection of response.collections) { + await client.deleteCollection(collection.name); + } + return { reset: true }; + }, + curateSources: function (sources = []) { + const documents = []; + for (const source of sources) { + if (Object.keys(source).length > 0) { + documents.push({ + ...source, + }); + } + } + + return documents; + }, +}; + +module.exports.QDrant = QDrant; diff --git a/server/yarn.lock b/server/yarn.lock index 2ff2aec4b..6a9e1669e 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -173,6 +173,25 @@ dependencies: cross-fetch "^3.1.5" +"@qdrant/js-client-rest@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@qdrant/js-client-rest/-/js-client-rest-1.4.0.tgz#efd341a9a30b241e7e11f773b581b3102db1adc6" + integrity sha512-I3pCKnaVdqiVpZ9+XtEjCx7IQSJnerXffD/g8mj/fZsOOJH3IFM+nF2izOfVIByufAArW+drGcAPrxHedba99w== + dependencies: + "@qdrant/openapi-typescript-fetch" "^1.2.1" + "@sevinf/maybe" "^0.5.0" + undici "^5.22.1" + +"@qdrant/openapi-typescript-fetch@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@qdrant/openapi-typescript-fetch/-/openapi-typescript-fetch-1.2.1.tgz#6e232899ca0a7fbc769f0c3a229b56f93da39f19" + integrity sha512-oiBJRN1ME7orFZocgE25jrM3knIF/OKJfMsZPBbtMMKfgNVYfps0MokGvSJkBmecj6bf8QoLXWIGlIoaTM4Zmw== + +"@sevinf/maybe@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@sevinf/maybe/-/maybe-0.5.0.tgz#e59fcea028df615fe87d708bb30e1f338e46bb44" + integrity sha512-ARhyoYDnY1LES3vYI0fiG6e9esWfTNcXcO6+MPJJXcnyMV3bim4lnFt45VXouV7y82F4x3YH8nOQ6VztuvUiWg== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -526,7 +545,7 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -busboy@^1.0.0: +busboy@^1.0.0, busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== @@ -2505,6 +2524,13 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== +undici@^5.22.1: + version "5.23.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.23.0.tgz#e7bdb0ed42cebe7b7aca87ced53e6eaafb8f8ca0" + integrity sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg== + dependencies: + busboy "^1.6.0" + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"