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"