From fc0431fca39460c44d99ddb46c00e08e6a7431a2 Mon Sep 17 00:00:00 2001 From: timothycarambat <rambat1010@gmail.com> Date: Thu, 8 Jun 2023 13:13:48 -0700 Subject: [PATCH] add endpoint saftey to prevent server dying in production --- frontend/src/components/DefaultChat/index.jsx | 45 ++++--- server/endpoints/chat.js | 23 ++-- server/endpoints/system.js | 69 +++++++---- server/endpoints/workspaces.js | 114 +++++++++++------- server/index.js | 44 ++++--- server/utils/chroma/index.js | 42 ++----- server/utils/helpers/index.js | 33 ++++- server/utils/pinecone/index.js | 45 ++----- 8 files changed, 226 insertions(+), 189 deletions(-) diff --git a/frontend/src/components/DefaultChat/index.jsx b/frontend/src/components/DefaultChat/index.jsx index d85cfd1c4..7f8e4444e 100644 --- a/frontend/src/components/DefaultChat/index.jsx +++ b/frontend/src/components/DefaultChat/index.jsx @@ -16,8 +16,9 @@ export default function DefaultChatContainer() { const MESSAGES = [ <React.Fragment> <div - className={`flex w-full mt-2 justify-start ${popMsg ? "chat__message" : "" - }`} + className={`flex w-full mt-2 justify-start ${ + popMsg ? "chat__message" : "" + }`} > <div className="p-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-b-2xl rounded-tr-2xl rounded-tl-sm"> <p className="text-slate-800 dark:text-slate-200 font-semibold"> @@ -33,8 +34,9 @@ export default function DefaultChatContainer() { <React.Fragment> <div - className={`flex w-full mt-2 justify-start ${popMsg ? "chat__message" : "" - }`} + className={`flex w-full mt-2 justify-start ${ + popMsg ? "chat__message" : "" + }`} > <div className="p-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-b-2xl rounded-tr-2xl rounded-tl-sm"> <p className="text-slate-800 dark:text-slate-200 font-semibold"> @@ -49,8 +51,9 @@ export default function DefaultChatContainer() { <React.Fragment> <div - className={`flex w-full mt-2 justify-start ${popMsg ? "chat__message" : "" - }`} + className={`flex w-full mt-2 justify-start ${ + popMsg ? "chat__message" : "" + }`} > <div className="p-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-b-2xl rounded-tr-2xl rounded-tl-sm"> <p className="text-slate-800 dark:text-slate-200 font-semibold"> @@ -76,8 +79,9 @@ export default function DefaultChatContainer() { <React.Fragment> <div - className={`flex w-full mt-2 justify-end ${popMsg ? "chat__message" : "" - }`} + className={`flex w-full mt-2 justify-end ${ + popMsg ? "chat__message" : "" + }`} > <div className="p-4 max-w-[75%] bg-slate-200 dark:bg-amber-800 rounded-b-2xl rounded-tl-2xl rounded-tr-sm"> <p className="text-slate-800 dark:text-slate-200 font-semibold"> @@ -89,8 +93,9 @@ export default function DefaultChatContainer() { <React.Fragment> <div - className={`flex w-full mt-2 justify-start ${popMsg ? "chat__message" : "" - }`} + className={`flex w-full mt-2 justify-start ${ + popMsg ? "chat__message" : "" + }`} > <div className="p-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-b-2xl rounded-tr-2xl rounded-tl-sm"> <p className="text-slate-800 dark:text-slate-200 font-semibold"> @@ -117,8 +122,9 @@ export default function DefaultChatContainer() { <React.Fragment> <div - className={`flex w-full mt-2 justify-end ${popMsg ? "chat__message" : "" - }`} + className={`flex w-full mt-2 justify-end ${ + popMsg ? "chat__message" : "" + }`} > <div className="p-4 max-w-[75%] bg-slate-200 dark:bg-amber-800 rounded-b-2xl rounded-tl-2xl rounded-tr-sm"> <p className="text-slate-800 dark:text-slate-200 font-semibold"> @@ -131,8 +137,9 @@ export default function DefaultChatContainer() { <React.Fragment> <div - className={`flex w-full mt-2 justify-start ${popMsg ? "chat__message" : "" - }`} + className={`flex w-full mt-2 justify-start ${ + popMsg ? "chat__message" : "" + }`} > <div className="p-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-b-2xl rounded-tr-2xl rounded-tl-sm"> <p className="text-slate-800 dark:text-slate-200 font-semibold"> @@ -161,8 +168,9 @@ export default function DefaultChatContainer() { <React.Fragment> <div - className={`flex w-full mt-2 justify-end ${popMsg ? "chat__message" : "" - }`} + className={`flex w-full mt-2 justify-end ${ + popMsg ? "chat__message" : "" + }`} > <div className="p-4 max-w-[75%] bg-slate-200 dark:bg-amber-800 rounded-b-2xl rounded-tl-2xl rounded-tr-sm"> <p className="text-slate-800 dark:text-slate-200 font-semibold"> @@ -174,8 +182,9 @@ export default function DefaultChatContainer() { <React.Fragment> <div - className={`flex w-full mt-2 justify-start ${popMsg ? "chat__message" : "" - }`} + className={`flex w-full mt-2 justify-start ${ + popMsg ? "chat__message" : "" + }`} > <div className="p-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-b-2xl rounded-tr-2xl rounded-tl-sm"> <p className="text-slate-800 dark:text-slate-200 font-semibold"> diff --git a/server/endpoints/chat.js b/server/endpoints/chat.js index dcc81fb5d..bf13041cb 100644 --- a/server/endpoints/chat.js +++ b/server/endpoints/chat.js @@ -6,16 +6,21 @@ function chatEndpoints(app) { if (!app) return; app.post("/workspace/:slug/chat", async (request, response) => { - const { slug } = request.params; - const { message, mode = "query" } = reqBody(request); - const workspace = await Workspace.get(`slug = '${slug}'`); - if (!workspace) { - response.sendStatus(400).end(); - return; - } + try { + const { slug } = request.params; + const { message, mode = "query" } = reqBody(request); + const workspace = await Workspace.get(`slug = '${slug}'`); + if (!workspace) { + response.sendStatus(400).end(); + return; + } - const result = await chatWithWorkspace(workspace, message, mode); - response.status(200).json({ ...result }); + const result = await chatWithWorkspace(workspace, message, mode); + response.status(200).json({ ...result }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } }); } diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 158e30003..f03cb368a 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -1,4 +1,6 @@ -if (process.env.NODE_ENV === 'development') require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` }); +process.env.NODE_ENV === "development" + ? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` }) + : require("dotenv").config(); const { viewLocalFiles } = require("../utils/files"); const { getVectorDbClass } = require("../utils/helpers"); @@ -10,37 +12,52 @@ function systemEndpoints(app) { }); app.get("/setup-complete", (_, response) => { - const vectorDB = process.env.VECTOR_DB || "pinecone"; - const results = { - VectorDB: vectorDB, - OpenAiKey: !!process.env.OPEN_AI_KEY, - OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo", - ...(vectorDB === "pinecone" - ? { - PineConeEnvironment: process.env.PINECONE_ENVIRONMENT, - PineConeKey: !!process.env.PINECONE_API_KEY, - PineConeIndex: process.env.PINECONE_INDEX, - } - : {}), - ...(vectorDB === "chroma" - ? { - ChromaEndpoint: process.env.CHROMA_ENDPOINT, - } - : {}), - }; - response.status(200).json({ results }); + try { + const vectorDB = process.env.VECTOR_DB || "pinecone"; + const results = { + VectorDB: vectorDB, + OpenAiKey: !!process.env.OPEN_AI_KEY, + OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo", + ...(vectorDB === "pinecone" + ? { + PineConeEnvironment: process.env.PINECONE_ENVIRONMENT, + PineConeKey: !!process.env.PINECONE_API_KEY, + PineConeIndex: process.env.PINECONE_INDEX, + } + : {}), + ...(vectorDB === "chroma" + ? { + ChromaEndpoint: process.env.CHROMA_ENDPOINT, + } + : {}), + }; + response.status(200).json({ results }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } }); app.get("/system-vectors", async (_, response) => { - const VectorDb = getVectorDbClass(); - const vectorCount = await VectorDb.totalIndicies(); - response.status(200).json({ vectorCount }); + try { + const VectorDb = getVectorDbClass(); + const vectorCount = await VectorDb.totalIndicies(); + response.status(200).json({ vectorCount }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } }); app.get("/local-files", async (_, response) => { - const localFiles = await viewLocalFiles(); - response.status(200).json({ localFiles }); + try { + const localFiles = await viewLocalFiles(); + response.status(200).json({ localFiles }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } }); } -module.exports = { systemEndpoints }; \ No newline at end of file +module.exports = { systemEndpoints }; diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js index d61ef3bed..d37b1ef61 100644 --- a/server/endpoints/workspaces.js +++ b/server/endpoints/workspaces.js @@ -10,70 +10,100 @@ function workspaceEndpoints(app) { if (!app) return; app.post("/workspace/new", async (request, response) => { - const { name = null } = reqBody(request); - const { workspace, message } = await Workspace.new(name); - response.status(200).json({ workspace, message }); + try { + const { name = null } = reqBody(request); + const { workspace, message } = await Workspace.new(name); + response.status(200).json({ workspace, message }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } }); app.post("/workspace/:slug/update-embeddings", async (request, response) => { - const { slug = null } = request.params; - const { adds = [], deletes = [] } = reqBody(request); - const currWorkspace = await Workspace.get(`slug = '${slug}'`); + try { + const { slug = null } = request.params; + const { adds = [], deletes = [] } = reqBody(request); + const currWorkspace = await Workspace.get(`slug = '${slug}'`); - if (!currWorkspace) { - response.sendStatus(400).end(); - return; - } + if (!currWorkspace) { + response.sendStatus(400).end(); + return; + } - await Document.removeDocuments(currWorkspace, deletes); - await Document.addDocuments(currWorkspace, adds); - const updatedWorkspace = await Workspace.get(`slug = '${slug}'`); - response.status(200).json({ workspace: updatedWorkspace }); + await Document.removeDocuments(currWorkspace, deletes); + await Document.addDocuments(currWorkspace, adds); + const updatedWorkspace = await Workspace.get(`slug = '${slug}'`); + response.status(200).json({ workspace: updatedWorkspace }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } }); app.delete("/workspace/:slug", async (request, response) => { - const VectorDb = getVectorDbClass(); - const { slug = "" } = request.params; - const workspace = await Workspace.get(`slug = '${slug}'`); + try { + const VectorDb = getVectorDbClass(); + const { slug = "" } = request.params; + const workspace = await Workspace.get(`slug = '${slug}'`); - if (!workspace) { - response.sendStatus(400).end(); - return; - } + if (!workspace) { + response.sendStatus(400).end(); + return; + } - await Workspace.delete(`slug = '${slug.toLowerCase()}'`); - await DocumentVectors.deleteForWorkspace(workspace.id); - await Document.delete(`workspaceId = ${Number(workspace.id)}`); - await WorkspaceChats.delete(`workspaceId = ${Number(workspace.id)}`); - try { - await VectorDb["delete-namespace"]({ namespace: slug }); + await Workspace.delete(`slug = '${slug.toLowerCase()}'`); + await DocumentVectors.deleteForWorkspace(workspace.id); + await Document.delete(`workspaceId = ${Number(workspace.id)}`); + await WorkspaceChats.delete(`workspaceId = ${Number(workspace.id)}`); + try { + await VectorDb["delete-namespace"]({ namespace: slug }); + } catch (e) { + console.error(e.message); + } + response.sendStatus(200).end(); } catch (e) { - console.error(e.message); + console.log(e.message, e); + response.sendStatus(500).end(); } - response.sendStatus(200).end(); }); app.get("/workspaces", async (_, response) => { - const workspaces = await Workspace.where(); - response.status(200).json({ workspaces }); + try { + const workspaces = await Workspace.where(); + response.status(200).json({ workspaces }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } }); app.get("/workspace/:slug", async (request, response) => { - const { slug } = request.params; - const workspace = await Workspace.get(`slug = '${slug}'`); - response.status(200).json({ workspace }); + try { + const { slug } = request.params; + const workspace = await Workspace.get(`slug = '${slug}'`); + response.status(200).json({ workspace }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } }); app.get("/workspace/:slug/chats", async (request, response) => { - const { slug } = request.params; - const workspace = await Workspace.get(`slug = '${slug}'`); - if (!workspace) { - response.sendStatus(400).end(); - return; - } + try { + const { slug } = request.params; + const workspace = await Workspace.get(`slug = '${slug}'`); + if (!workspace) { + response.sendStatus(400).end(); + return; + } - const history = await WorkspaceChats.forWorkspace(workspace.id); - response.status(200).json({ history: convertToChatHistory(history) }); + const history = await WorkspaceChats.forWorkspace(workspace.id); + response.status(200).json({ history: convertToChatHistory(history) }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } }); } diff --git a/server/index.js b/server/index.js index 00a412faa..648d34c03 100644 --- a/server/index.js +++ b/server/index.js @@ -1,4 +1,7 @@ -if (process.env.NODE_ENV === 'development') require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` }); +process.env.NODE_ENV === "development" + ? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` }) + : require("dotenv").config(); + const express = require("express"); const bodyParser = require("body-parser"); const cors = require("cors"); @@ -25,26 +28,31 @@ workspaceEndpoints(app); chatEndpoints(app); app.post("/v/:command", async (request, response) => { - const VectorDb = getVectorDbClass(); - const { command } = request.params; - if (!Object.getOwnPropertyNames(VectorDb).includes(command)) { - response.status(500).json({ - message: "invalid interface command", - commands: Object.getOwnPropertyNames(VectorDb), - }); - return; - } - try { - const body = reqBody(request); - const resBody = await VectorDb[command](body); - response.status(200).json({ ...resBody }); + const VectorDb = getVectorDbClass(); + const { command } = request.params; + if (!Object.getOwnPropertyNames(VectorDb).includes(command)) { + response.status(500).json({ + message: "invalid interface command", + commands: Object.getOwnPropertyNames(VectorDb), + }); + return; + } + + try { + const body = reqBody(request); + const resBody = await VectorDb[command](body); + response.status(200).json({ ...resBody }); + } catch (e) { + // console.error(e) + console.error(JSON.stringify(e)); + response.status(500).json({ error: e.message }); + } + return; } catch (e) { - // console.error(e) - console.error(JSON.stringify(e)); - response.status(500).json({ error: e.message }); + console.log(e.message, e); + response.sendStatus(500).end(); } - return; }); app.all("*", function (_, response) { diff --git a/server/utils/chroma/index.js b/server/utils/chroma/index.js index 92dcec928..002041419 100644 --- a/server/utils/chroma/index.js +++ b/server/utils/chroma/index.js @@ -2,46 +2,20 @@ const { ChromaClient, OpenAIEmbeddingFunction } = require("chromadb"); const { Chroma: ChromaStore } = require("langchain/vectorstores/chroma"); const { OpenAI } = require("langchain/llms/openai"); const { ChatOpenAI } = require("langchain/chat_models/openai"); -const { - VectorDBQAChain, - LLMChain, - RetrievalQAChain, - ConversationalRetrievalQAChain, -} = require("langchain/chains"); +const { VectorDBQAChain } = require("langchain/chains"); const { OpenAIEmbeddings } = require("langchain/embeddings/openai"); -// const { VectorStoreRetrieverMemory, BufferMemory } = require("langchain/memory"); -// const { PromptTemplate } = require("langchain/prompts"); const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter"); const { storeVectorResult, cachedVectorInformation } = require("../files"); const { Configuration, OpenAIApi } = require("openai"); const { v4: uuidv4 } = require("uuid"); - -const toChunks = (arr, size) => { - return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) => - arr.slice(i * size, i * size + size) - ); -}; - -function curateSources(sources = []) { - const knownDocs = []; - const documents = []; - for (const source of sources) { - const { metadata = {} } = source; - if ( - Object.keys(metadata).length > 0 && - !knownDocs.includes(metadata.title) - ) { - documents.push({ ...metadata }); - knownDocs.push(metadata.title); - } - } - - return documents; -} +const { toChunks, curateSources } = require("../helpers"); const Chroma = { - name: 'Chroma', + name: "Chroma", connect: async function () { + if (process.env.VECTOR_DB !== "chroma") + throw new Error("Chroma::Invalid ENV settings"); + const client = new ChromaClient({ path: process.env.CHROMA_ENDPOINT, // if not set will fallback to localhost:8000 }); @@ -356,6 +330,4 @@ const Chroma = { }, }; -module.exports = { - Chroma, -}; +module.exports.Chroma = Chroma; diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index aa4cf3e89..560ac1b76 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -1,7 +1,7 @@ -const { Pinecone } = require("../pinecone"); -const { Chroma } = require("../chroma"); - function getVectorDbClass() { + const { Pinecone } = require("../pinecone"); + const { Chroma } = require("../chroma"); + const vectorSelection = process.env.VECTOR_DB || "pinecone"; switch (vectorSelection) { case "pinecone": @@ -9,10 +9,35 @@ function getVectorDbClass() { case "chroma": return Chroma; default: - return Pinecone; + throw new Error("ENV: No VECTOR_DB value found in environment!"); } } +function toChunks(arr, size) { + return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) => + arr.slice(i * size, i * size + size) + ); +} + +function curateSources(sources = []) { + const knownDocs = []; + const documents = []; + for (const source of sources) { + const { metadata = {} } = source; + if ( + Object.keys(metadata).length > 0 && + !knownDocs.includes(metadata.title) + ) { + documents.push({ ...metadata }); + knownDocs.push(metadata.title); + } + } + + return documents; +} + module.exports = { getVectorDbClass, + toChunks, + curateSources, }; diff --git a/server/utils/pinecone/index.js b/server/utils/pinecone/index.js index be217854e..e57c34a8a 100644 --- a/server/utils/pinecone/index.js +++ b/server/utils/pinecone/index.js @@ -2,49 +2,22 @@ const { PineconeClient } = require("@pinecone-database/pinecone"); const { PineconeStore } = require("langchain/vectorstores/pinecone"); const { OpenAI } = require("langchain/llms/openai"); const { ChatOpenAI } = require("langchain/chat_models/openai"); -const { - VectorDBQAChain, - LLMChain, - RetrievalQAChain, - ConversationalRetrievalQAChain, -} = require("langchain/chains"); +const { VectorDBQAChain, LLMChain } = require("langchain/chains"); const { OpenAIEmbeddings } = require("langchain/embeddings/openai"); -const { - VectorStoreRetrieverMemory, - BufferMemory, -} = require("langchain/memory"); +const { VectorStoreRetrieverMemory } = require("langchain/memory"); const { PromptTemplate } = require("langchain/prompts"); const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter"); const { storeVectorResult, cachedVectorInformation } = require("../files"); const { Configuration, OpenAIApi } = require("openai"); const { v4: uuidv4 } = require("uuid"); - -const toChunks = (arr, size) => { - return Array.from({ length: Math.ceil(arr.length / size) }, (_v, i) => - arr.slice(i * size, i * size + size) - ); -}; - -function curateSources(sources = []) { - const knownDocs = []; - const documents = []; - for (const source of sources) { - const { metadata = {} } = source; - if ( - Object.keys(metadata).length > 0 && - !knownDocs.includes(metadata.title) - ) { - documents.push({ ...metadata }); - knownDocs.push(metadata.title); - } - } - - return documents; -} +const { toChunks, curateSources } = require("../helpers"); const Pinecone = { - name: 'Pinecone', + name: "Pinecone", connect: async function () { + if (process.env.VECTOR_DB !== "pinecone") + throw new Error("Pinecone::Invalid ENV settings"); + const client = new PineconeClient(); await client.init({ apiKey: process.env.PINECONE_API_KEY, @@ -327,6 +300,4 @@ const Pinecone = { }, }; -module.exports = { - Pinecone, -}; +module.exports.Pinecone = Pinecone; -- GitLab