add endpoint saftey to prevent server dying in production

This commit is contained in:
timothycarambat 2023-06-08 13:13:48 -07:00
parent abdaf4e63d
commit fc0431fca3
8 changed files with 227 additions and 190 deletions
frontend/src/components/DefaultChat
server

View file

@ -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">

View file

@ -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();
}
});
}

View file

@ -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 };
module.exports = { systemEndpoints };

View file

@ -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 });
} catch (e) {
console.log(e.message, e);
response.sendStatus(500).end();
}
await Document.removeDocuments(currWorkspace, deletes);
await Document.addDocuments(currWorkspace, adds);
const updatedWorkspace = await Workspace.get(`slug = '${slug}'`);
response.status(200).json({ workspace: updatedWorkspace });
});
app.delete("/workspace/:slug", async (request, response) => {
const VectorDb = getVectorDbClass();
const { slug = "" } = request.params;
const workspace = await Workspace.get(`slug = '${slug}'`);
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 });
const VectorDb = getVectorDbClass();
const { slug = "" } = request.params;
const workspace = await Workspace.get(`slug = '${slug}'`);
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 });
} 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();
}
});
}

View file

@ -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) {

View file

@ -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;

View file

@ -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,
};

View file

@ -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;