From 1f29cec9182b63141207f3cd09d568ac1cc06a8d Mon Sep 17 00:00:00 2001 From: Timothy Carambat <rambat1010@gmail.com> Date: Fri, 4 Aug 2023 14:56:27 -0700 Subject: [PATCH] Multiple LLM Support framework + AzureOpenAI Support (#180) * Remove LangchainJS for chat support chaining Implement runtime LLM selection Implement AzureOpenAI Support for LLM + Emebedding WIP on frontend Update env to reflect the new fields * Remove LangchainJS for chat support chaining Implement runtime LLM selection Implement AzureOpenAI Support for LLM + Emebedding WIP on frontend Update env to reflect the new fields * Replace keys with LLM Selection in settings modal Enforce checks for new ENVs depending on LLM selection --- .vscode/settings.json | 5 + docker/.env.example | 21 +- .../components/Modals/Settings/Keys/index.jsx | 220 -------------- .../Modals/Settings/LLMSelection/index.jsx | 281 ++++++++++++++++++ .../Modals/Settings/VectorDbs/index.jsx | 4 +- .../src/components/Modals/Settings/index.jsx | 25 +- frontend/src/media/llmprovider/anthropic.png | Bin 0 -> 11892 bytes frontend/src/media/llmprovider/azure.png | Bin 0 -> 34705 bytes frontend/src/media/llmprovider/openai.png | Bin 0 -> 22744 bytes server/.env.example | 21 +- server/endpoints/system.js | 21 +- server/package.json | 3 +- server/utils/AiProviders/azureOpenAi/index.js | 99 ++++++ server/utils/AiProviders/openAi/index.js | 3 +- server/utils/chats/index.js | 13 +- server/utils/helpers/index.js | 22 +- server/utils/helpers/updateENV.js | 41 +++ .../utils/vectorDbProviders/chroma/index.js | 72 ++--- server/utils/vectorDbProviders/lance/index.js | 20 +- .../utils/vectorDbProviders/pinecone/index.js | 58 ++-- server/yarn.lock | 106 +++++++ 21 files changed, 699 insertions(+), 336 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 frontend/src/components/Modals/Settings/Keys/index.jsx create mode 100644 frontend/src/components/Modals/Settings/LLMSelection/index.jsx create mode 100644 frontend/src/media/llmprovider/anthropic.png create mode 100644 frontend/src/media/llmprovider/azure.png create mode 100644 frontend/src/media/llmprovider/openai.png create mode 100644 server/utils/AiProviders/azureOpenAi/index.js diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..450dd7797 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "openai" + ] +} \ No newline at end of file diff --git a/docker/.env.example b/docker/.env.example index e44acd022..6b9791eb5 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -1,8 +1,24 @@ SERVER_PORT=3001 -OPEN_AI_KEY= -OPEN_MODEL_PREF='gpt-3.5-turbo' CACHE_VECTORS="true" +# JWT_SECRET="my-random-string-for-seeding" # Only needed if AUTH_TOKEN is set. Please generate random string at least 12 chars long. + +########################################### +######## LLM API SElECTION ################ +########################################### +LLM_PROVIDER='openai' +# OPEN_AI_KEY= +OPEN_MODEL_PREF='gpt-3.5-turbo' +# LLM_PROVIDER='azure' +# AZURE_OPENAI_ENDPOINT= +# AZURE_OPENAI_KEY= +# OPEN_MODEL_PREF='my-gpt35-deployment' # This is the "deployment" on Azure you want to use. Not the base model. +# EMBEDDING_MODEL_PREF='embedder-model' # This is the "deployment" on Azure you want to use for embeddings. Not the base model. Valid base model is text-embedding-ada-002 + + +########################################### +######## Vector Database Selection ######## +########################################### # Enable all below if you are using vector database: Chroma. # VECTOR_DB="chroma" # CHROMA_ENDPOINT='http://localhost:8000' @@ -18,7 +34,6 @@ PINECONE_INDEX= # CLOUD DEPLOYMENT VARIRABLES ONLY # AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting. -# JWT_SECRET="my-random-string-for-seeding" # Only needed if AUTH_TOKEN is set. Please generate random string at least 12 chars long. # NO_DEBUG="true" STORAGE_DIR="./server/storage" GOOGLE_APIS_KEY= diff --git a/frontend/src/components/Modals/Settings/Keys/index.jsx b/frontend/src/components/Modals/Settings/Keys/index.jsx deleted file mode 100644 index 84b75373d..000000000 --- a/frontend/src/components/Modals/Settings/Keys/index.jsx +++ /dev/null @@ -1,220 +0,0 @@ -import React, { useState } from "react"; -import { AlertCircle, Loader } from "react-feather"; -import System from "../../../../models/system"; - -const noop = () => false; -export default function SystemKeys({ hideModal = noop, user, settings = {} }) { - const canDebug = settings.MultiUserMode - ? settings?.CanDebug && user?.role === "admin" - : settings?.CanDebug; - function validSettings(settings) { - return ( - settings?.OpenAiKey && - !!settings?.OpenAiModelPref && - !!settings?.VectorDB && - (settings?.VectorDB === "chroma" ? !!settings?.ChromaEndpoint : true) && - (settings?.VectorDB === "pinecone" - ? !!settings?.PineConeKey && - !!settings?.PineConeEnvironment && - !!settings?.PineConeIndex - : true) - ); - } - - return ( - <div className="relative w-full max-w-2xl max-h-full"> - <div className="relative bg-white rounded-lg shadow dark:bg-stone-700"> - <div className="flex items-start justify-between px-6 py-4"> - <p className="text-gray-800 dark:text-stone-200 text-base "> - These are the credentials and settings for how your AnythingLLM - instance will function. Its important these keys are current and - correct. - </p> - </div> - <div className="p-6 space-y-6 flex h-full w-full"> - <div className="w-full flex flex-col gap-y-4"> - {!validSettings(settings) && ( - <div className="bg-orange-300 p-4 rounded-lg border border-orange-600 text-orange-700 w-full items-center flex gap-x-2"> - <AlertCircle className="h-8 w-8" /> - <p className="text-sm md:text-base "> - Ensure all fields are green before attempting to use - AnythingLLM or it may not function as expected! - </p> - </div> - )} - <ShowKey - name="OpenAI API Key" - env="OpenAiKey" - value={settings?.OpenAiKey ? "*".repeat(20) : ""} - valid={settings?.OpenAiKey} - allowDebug={canDebug} - /> - <ShowKey - name="OpenAI Model for chats" - env="OpenAiModelPref" - value={settings?.OpenAiModelPref} - valid={!!settings?.OpenAiModelPref} - allowDebug={canDebug} - /> - </div> - </div> - <div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600"> - <button - onClick={hideModal} - type="button" - className="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600" - > - Close - </button> - </div> - </div> - </div> - ); -} - -function ShowKey({ name, env, value, valid, allowDebug = true }) { - const [isValid, setIsValid] = useState(valid); - const [debug, setDebug] = useState(false); - const [saving, setSaving] = useState(false); - const handleSubmit = async (e) => { - e.preventDefault(); - setSaving(true); - const data = {}; - const form = new FormData(e.target); - for (var [key, value] of form.entries()) data[key] = value; - const { error } = await System.updateSystem(data); - if (!!error) { - alert(error); - setSaving(false); - setIsValid(false); - return; - } - - setSaving(false); - setDebug(false); - setIsValid(true); - }; - - if (!isValid) { - return ( - <form onSubmit={handleSubmit}> - <div> - <label - htmlFor="error" - className="block mb-2 text-sm font-medium text-red-700 dark:text-red-500" - > - {name} - </label> - <input - type="text" - id="error" - name={env} - disabled={!debug} - className="bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 dark:bg-gray-700 focus:border-red-500 block w-full p-2.5 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500" - placeholder={name} - defaultValue={value} - required={true} - autoComplete="off" - /> - <div className="flex items-center justify-between"> - <p className="mt-2 text-sm text-red-600 dark:text-red-500"> - Need setup in .env file. - </p> - {allowDebug && ( - <> - {debug ? ( - <div className="flex items-center gap-x-2 mt-2"> - {saving ? ( - <> - <Loader className="animate-spin h-4 w-4 text-slate-300 dark:text-slate-500" /> - </> - ) : ( - <> - <button - type="button" - onClick={() => setDebug(false)} - className="text-xs text-slate-300 dark:text-slate-500" - > - Cancel - </button> - <button - type="submit" - className="text-xs text-blue-300 dark:text-blue-500" - > - Save - </button> - </> - )} - </div> - ) : ( - <button - type="button" - onClick={() => setDebug(true)} - className="mt-2 text-xs text-slate-300 dark:text-slate-500" - > - Change - </button> - )} - </> - )} - </div> - </div> - </form> - ); - } - - return ( - <form onSubmit={handleSubmit}> - <div className="mb-6"> - <label - htmlFor="success" - className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200" - > - {name} - </label> - <input - type="text" - id="success" - name={env} - disabled={!debug} - className="border border-white text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 text-sm rounded-lg focus:ring-green-500 focus:border-green-500 block w-full p-2.5 dark:bg-gray-700 dark:border-green-500" - defaultValue={value} - required={true} - autoComplete="off" - /> - {allowDebug && ( - <div className="flex items-center justify-end"> - {debug ? ( - <div className="flex items-center gap-x-2 mt-2"> - {saving ? ( - <> - <Loader className="animate-spin h-4 w-4 text-slate-300 dark:text-slate-500" /> - </> - ) : ( - <> - <button - onClick={() => setDebug(false)} - className="text-xs text-slate-300 dark:text-slate-500" - > - Cancel - </button> - <button className="text-xs text-blue-300 dark:text-blue-500"> - Save - </button> - </> - )} - </div> - ) : ( - <button - onClick={() => setDebug(true)} - className="mt-2 text-xs text-slate-300 dark:text-slate-500" - > - Change - </button> - )} - </div> - )} - </div> - </form> - ); -} diff --git a/frontend/src/components/Modals/Settings/LLMSelection/index.jsx b/frontend/src/components/Modals/Settings/LLMSelection/index.jsx new file mode 100644 index 000000000..94b75ace8 --- /dev/null +++ b/frontend/src/components/Modals/Settings/LLMSelection/index.jsx @@ -0,0 +1,281 @@ +import React, { useState } from "react"; +import System from "../../../../models/system"; +import OpenAiLogo from "../../../../media/llmprovider/openai.png"; +import AzureOpenAiLogo from "../../../../media/llmprovider/azure.png"; +import AnthropicLogo from "../../../../media/llmprovider/anthropic.png"; + +const noop = () => false; +export default function LLMSelection({ + hideModal = noop, + user, + settings = {}, +}) { + const [hasChanges, setHasChanges] = useState(false); + const [llmChoice, setLLMChoice] = useState(settings?.LLMProvider || "openai"); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + const canDebug = settings.MultiUserMode + ? settings?.CanDebug && user?.role === "admin" + : settings?.CanDebug; + + function updateLLMChoice(selection) { + if (!canDebug || selection === llmChoice) return false; + setHasChanges(true); + setLLMChoice(selection); + } + + const handleSubmit = async (e) => { + e.preventDefault(); + setSaving(true); + setError(null); + const data = {}; + const form = new FormData(e.target); + for (var [key, value] of form.entries()) data[key] = value; + const { error } = await System.updateSystem(data); + setError(error); + setSaving(false); + setHasChanges(!!error ? true : false); + }; + return ( + <div className="relative w-full max-w-2xl max-h-full"> + <div className="relative bg-white rounded-lg shadow dark:bg-stone-700"> + <div className="flex items-start justify-between px-6 py-4"> + <p className="text-gray-800 dark:text-stone-200 text-base "> + These are the credentials and settings for your preferred LLM chat & + embedding provider. Its important these keys are current and correct + or else AnythingLLM will not function properly. + </p> + </div> + + {!!error && ( + <div className="mb-8 bg-red-700 dark:bg-orange-800 bg-opacity-30 border border-red-800 dark:border-orange-600 p-4 rounded-lg w-[90%] flex mx-auto"> + <p className="text-red-800 dark:text-orange-300 text-sm">{error}</p> + </div> + )} + + <form onSubmit={handleSubmit} onChange={() => setHasChanges(true)}> + <div className="px-6 space-y-6 flex h-full w-full"> + <div className="w-full flex flex-col gap-y-4"> + <p className="block text-sm font-medium text-gray-800 dark:text-slate-200"> + LLM providers + </p> + <div className="w-full flex overflow-x-scroll gap-x-4 no-scroll"> + <input hidden={true} name="LLMProvider" value={llmChoice} /> + <LLMProviderOption + name="OpenAI" + value="openai" + link="openai.com" + description="The standard option for most non-commercial use. Provides both chat and embedding." + checked={llmChoice === "openai"} + image={OpenAiLogo} + onClick={updateLLMChoice} + /> + <LLMProviderOption + name="Azure OpenAi" + value="azure" + link="azure.microsoft.com" + description="The enterprise option of OpenAI hosted on Azure services. Provides both chat and embedding." + checked={llmChoice === "azure"} + image={AzureOpenAiLogo} + onClick={updateLLMChoice} + /> + <LLMProviderOption + name="Anthropic Claude 2" + value="anthropic-claude-2" + link="anthropic.com" + description="[COMING SOON] A friendly AI Assistant hosted by Anthropic. Provides chat services only!" + checked={llmChoice === "anthropic-claude-2"} + image={AnthropicLogo} + /> + </div> + {llmChoice === "openai" && ( + <> + <div> + <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"> + API Key + </label> + <input + type="text" + name="OpenAiKey" + 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="OpenAI API Key" + defaultValue={settings?.OpenAiKey ? "*".repeat(20) : ""} + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> + + <div> + <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"> + Chat Model Selection + </label> + <select + disabled={!canDebug} + name="OpenAiModelPref" + defaultValue={settings?.OpenAiModelPref} + required={true} + className="bg-gray-50 border border-gray-500 text-gray-900 text-sm rounded-lg block w-full p-2.5 dark:bg-stone-700 dark:border-slate-200 dark:placeholder-stone-500 dark:text-slate-200" + > + {[ + "gpt-3.5-turbo", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-16k", + "gpt-4", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0613", + ].map((model) => { + return ( + <option key={model} value={model}> + {model} + </option> + ); + })} + </select> + </div> + </> + )} + + {llmChoice === "azure" && ( + <> + <div> + <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"> + Azure Service Endpoint + </label> + <input + type="url" + name="AzureOpenAiEndpoint" + 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="https://my-azure.openai.azure.com" + defaultValue={settings?.AzureOpenAiEndpoint} + 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="AzureOpenAiKey" + 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="Azure OpenAI API Key" + defaultValue={ + settings?.AzureOpenAiKey ? "*".repeat(20) : "" + } + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> + + <div> + <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"> + Chat Model Deployment Name + </label> + <input + type="text" + name="AzureOpenAiModelPref" + 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="Azure OpenAI chat model deployment name" + defaultValue={settings?.AzureOpenAiModelPref} + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> + + <div> + <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200"> + Embedding Model Deployment Name + </label> + <input + type="text" + name="AzureOpenAiEmbeddingModelPref" + 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="Azure OpenAI embedding model deployment name" + defaultValue={settings?.AzureOpenAiEmbeddingModelPref} + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> + </> + )} + + {llmChoice === "anthropic-claude-2" && ( + <div className="w-full h-40 items-center justify-center flex"> + <p className="text-gray-800 dark:text-slate-400"> + This provider is unavailable and cannot be used in + AnythingLLM currently. + </p> + </div> + )} + </div> + </div> + <div className="w-full p-4"> + <button + hidden={!hasChanges} + disabled={saving} + type="submit" + className="w-full text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600" + > + {saving ? "Saving..." : "Save changes"} + </button> + </div> + </form> + <div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600"> + <button + onClick={hideModal} + type="button" + className="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600" + > + Close + </button> + </div> + </div> + </div> + ); +} + +const LLMProviderOption = ({ + name, + link, + description, + value, + image, + checked = false, + onClick, +}) => { + return ( + <div onClick={() => onClick(value)}> + <input + type="checkbox" + value={value} + className="peer hidden" + checked={checked} + readOnly={true} + formNoValidate={true} + /> + <label className="transition-all duration-300 inline-flex h-full w-60 cursor-pointer items-center justify-between rounded-lg border border-gray-200 bg-white p-5 text-gray-500 hover:bg-gray-50 hover:text-gray-600 peer-checked:border-blue-600 peer-checked:bg-blue-50 peer-checked:dark:bg-stone-800 peer-checked:text-gray-600 dark:border-slate-200 dark:bg-stone-800 dark:text-slate-400 dark:hover:bg-stone-700 dark:hover:text-slate-300 dark:peer-checked:text-slate-300"> + <div className="block"> + <img src={image} alt={name} className="mb-2 h-10 w-10 rounded-full" /> + <div className="w-full text-lg font-semibold">{name}</div> + <div className="flex w-full flex-col gap-y-1 text-sm"> + <p className="text-xs text-slate-400">{link}</p> + {description} + </div> + </div> + </label> + </div> + ); +}; diff --git a/frontend/src/components/Modals/Settings/VectorDbs/index.jsx b/frontend/src/components/Modals/Settings/VectorDbs/index.jsx index 0c4f5a386..c4ad0aec1 100644 --- a/frontend/src/components/Modals/Settings/VectorDbs/index.jsx +++ b/frontend/src/components/Modals/Settings/VectorDbs/index.jsx @@ -57,7 +57,7 @@ export default function VectorDBSelection({ <div className="px-6 space-y-6 flex h-full w-full"> <div className="w-full flex flex-col gap-y-4"> <p className="block text-sm font-medium text-gray-800 dark:text-slate-200"> - Vector database provider + Vector database providers </p> <div className="w-full flex overflow-x-scroll gap-x-4 no-scroll"> <input hidden={true} name="VectorDB" value={vectorDB} /> @@ -96,7 +96,7 @@ export default function VectorDBSelection({ Pinecone DB API Key </label> <input - type="text" + type="password" name="PineConeKey" 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" diff --git a/frontend/src/components/Modals/Settings/index.jsx b/frontend/src/components/Modals/Settings/index.jsx index 246b9ad37..bdf8e6e56 100644 --- a/frontend/src/components/Modals/Settings/index.jsx +++ b/frontend/src/components/Modals/Settings/index.jsx @@ -1,15 +1,22 @@ import React, { useEffect, useState } from "react"; -import { Archive, Lock, Key, X, Users, Database } from "react-feather"; -import SystemKeys from "./Keys"; +import { + Archive, + Lock, + X, + Users, + Database, + MessageSquare, +} from "react-feather"; import ExportOrImportData from "./ExportImport"; import PasswordProtection from "./PasswordProtection"; import System from "../../../models/system"; import MultiUserMode from "./MultiUserMode"; import useUser from "../../../hooks/useUser"; import VectorDBSelection from "./VectorDbs"; +import LLMSelection from "./LLMSelection"; const TABS = { - keys: SystemKeys, + llm: LLMSelection, exportimport: ExportOrImportData, password: PasswordProtection, multiuser: MultiUserMode, @@ -20,9 +27,9 @@ const noop = () => false; export default function SystemSettingsModal({ hideModal = noop }) { const { user } = useUser(); const [loading, setLoading] = useState(true); - const [selectedTab, setSelectedTab] = useState("keys"); + const [selectedTab, setSelectedTab] = useState("llm"); const [settings, setSettings] = useState(null); - const Component = TABS[selectedTab || "keys"]; + const Component = TABS[selectedTab || "llm"]; useEffect(() => { async function fetchKeys() { @@ -87,10 +94,10 @@ function SettingTabs({ selectedTab, changeTab, settings, user }) { return ( <ul className="flex overflow-x-scroll no-scroll -mb-px text-sm gap-x-2 font-medium text-center text-gray-500 dark:text-gray-400"> <SettingTab - active={selectedTab === "keys"} - displayName="Keys" - tabName="keys" - icon={<Key className="h-4 w-4 flex-shrink-0" />} + active={selectedTab === "llm"} + displayName="LLM Choice" + tabName="llm" + icon={<MessageSquare className="h-4 w-4 flex-shrink-0" />} onClick={changeTab} /> <SettingTab diff --git a/frontend/src/media/llmprovider/anthropic.png b/frontend/src/media/llmprovider/anthropic.png new file mode 100644 index 0000000000000000000000000000000000000000..09ff1535ff00e0c4761063b4972eca905980a5d6 GIT binary patch literal 11892 zcmeHtXINF+vL&J-N)7@N1XMso!X~pxf|8@4NKTuaHer*K2!cowL84>{isURHk`W{w z5G09+NX{7vueRsD^X|E~yMJ`|kN&=XDxW&nTyw2Ct7_D!Q7cqkRgUNa?FAeh93lnz zI~q7RrxdY2_;@hl%}#X|{yOg{uj7J)L)?P>IrV~rm<|R-F*4ei+h}`xa|aBLhcnU) zc}Ggt$=*`{;pA{1DR9#U$<HUm$0q{6`FRlnJbc0;B8Zz9BrhM2Fb_X3Pr#Wj8XTN6 zQ`YyiG1@B1NEF(h%ft+AYR=_h?+EMT;D~u3;h*;A7!xKBdpidgq=z`$?-@w=H+Gnt zP2vL6KYochn^_<=?#TXQD*R8J%?g8YL~?VxySsC_^Kqe_Ex8dQA|l*8yxhFJoG^pa z#nS;};=$?Qaurtk`%-tzT~N-}ju>mS0~2;>6H~M+Mx2f9uUUV-k{Rl+$2ht=+x>os z8H(H7&K!F#F5Cz%#NXc1428s4W9-cT<;A7!F#j@QXN}zn($2)eQk>0$)6Cq$#MKVN zCUM{4_x)x}X67!|mJUpuOnhS8*cY_3{-+gwkNka6?*GA~e^2|To&Ikgf)obhY;F42 zK{&W@nfxAc#hxA)7?#__40{wje>;jl??OtMx%|_MV|Suy?({Dsa2VJNjwa47=Jz}u z&BfU?%w5o~&M5PL_JDsZc-z_B1Y>R{!OO$T$H^ni$-^(k{V$jPGNRZ&F35Ob6wD+{ z5GWxFGag<}UO{sqPJSMg5T~#i0>LRPj1V$I2_QsxM9ltL>(5*MZ6#S#SV(|ZfLBmJ z1i_CG5)lyQ`RktF7ys>+duUgb6?R(^ynoI7ud)A{{y#DE-;e7*xBB;E{l8>r$A5|{ ztd)O<E7rbn0qo27H~YdEQpy!$g?5&>XKl}P+r-(#-qZvmhMoMM7ye~)|L3j<fblm! z{Ob)pOdS8s17R}qx3{33@1fCl61Pnp+)Tu{|8@9(^^$+V&+iik{P`!(<^Bu5BrGY} z9^v3z-%z+Cb<gAF@`SY}UDqSlZ$f7iT5sZ^PU8hO;-P7Lx=pE0watD#9vl$wC-a?c zA5Y^s;y{`nOk+Mpq&^uN5R_*M^*vX1DNu(9ujPiIf(YY}=dB_;3fwOqKimy<6wY(e z*CBuNAn`@=GYv&jvi=7nJqNf?o@6ovK7S$~Af<5U)ra(JS2T>($;sGxu7^IMq=~(A z=K`VPl`CiQSjeJr|9jg1%LiiGdw|=atDz{*&dI5Xe~LLM&PDIY3-g}psUcyflC)yz z(UGrkit0@+u3PwYI4P&D;>hEhG&3HrofGptphO@LIgi6*$))i<>J1NP{f~)6MMZUu zVn}ft(_1ELWvr~M?)doB#W`O`BCd0v${N}}KJq^7YB*H;)YGG@#X&CpZS~~1AtOB< zXSm2ns9;>(@vM`NPf)|rGTITDQaoZeY7;N(xw|?kC)LO>UhB^7^O8$KBCGDqtJt+) zr`J5hw6(Pf8Os)W*VahCHl*>@C*BPDP+>dovA@Ams9*EQm?v*p;7p(tK67z-c~D_t zq2oSAgy%Z9a8~HMii)$j1Lqqiqtwf^{cD0-TV*`N7=)2XUh(VG)6;kF-;Zc&YI1a( ze{)}veQI-m?O>Y&cG;D-{l|!jx*>Uey?8=G!maB{Ta(|UpEy1{gL|sHveMYzetFQT z?8nSZW@#yTNlD2lQZL+=7%z~9kx|absA%3?ealqc5ck)wUyj1`Uq(hs%Evp@U#6zE z_4TFCOR)ysa(2G3u(;gO(a|?VFW6&?VXB#MSXy1ZN<(PF9+gQDxIvzflqBu%�z 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 Ox)o$p?-WWKKl%@H2zy@u literal 0 HcmV?d00001 diff --git a/frontend/src/media/llmprovider/azure.png b/frontend/src/media/llmprovider/azure.png new file mode 100644 index 0000000000000000000000000000000000000000..4ce890ca26b4192029b44685f2c3f8f4295b41fb GIT binary patch literal 34705 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%0D6^lD%Ne{F(kJ1h>Kn+^P7~ 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 literal 0 HcmV?d00001 diff --git a/frontend/src/media/llmprovider/openai.png b/frontend/src/media/llmprovider/openai.png new file mode 100644 index 0000000000000000000000000000000000000000..b720ec753d1108c5f02107c3e4e46d1e3d9b6437 GIT binary patch literal 22744 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 literal 0 HcmV?d00001 diff --git a/server/.env.example b/server/.env.example index f671f78df..e06c0f7e7 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,8 +1,23 @@ SERVER_PORT=3001 -OPEN_AI_KEY= -OPEN_MODEL_PREF='gpt-3.5-turbo' CACHE_VECTORS="true" +JWT_SECRET="my-random-string-for-seeding" # Please generate random string at least 12 chars long. + +########################################### +######## LLM API SElECTION ################ +########################################### +LLM_PROVIDER='openai' +# OPEN_AI_KEY= +OPEN_MODEL_PREF='gpt-3.5-turbo' +# LLM_PROVIDER='azure' +# AZURE_OPENAI_ENDPOINT= +# AZURE_OPENAI_KEY= +# OPEN_MODEL_PREF='my-gpt35-deployment' # This is the "deployment" on Azure you want to use. Not the base model. +# EMBEDDING_MODEL_PREF='embedder-model' # This is the "deployment" on Azure you want to use for embeddings. Not the base model. Valid base model is text-embedding-ada-002 + +########################################### +######## Vector Database Selection ######## +########################################### # Enable all below if you are using vector database: Chroma. # VECTOR_DB="chroma" # CHROMA_ENDPOINT='http://localhost:8000' @@ -16,8 +31,6 @@ PINECONE_INDEX= # Enable all below if you are using vector database: LanceDB. # VECTOR_DB="lancedb" -JWT_SECRET="my-random-string-for-seeding" # Please generate random string at least 12 chars long. - # CLOUD DEPLOYMENT VARIRABLES ONLY # AUTH_TOKEN="hunter2" # This is the password to your application if remote hosting. # STORAGE_DIR= # absolute filesystem path with no trailing slash diff --git a/server/endpoints/system.js b/server/endpoints/system.js index e78af6add..98354f4a9 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -38,17 +38,16 @@ function systemEndpoints(app) { app.get("/setup-complete", async (_, response) => { try { + const llmProvider = process.env.LLM_PROVIDER || "openai"; const vectorDB = process.env.VECTOR_DB || "pinecone"; const results = { CanDebug: !!!process.env.NO_DEBUG, RequiresAuth: !!process.env.AUTH_TOKEN, - VectorDB: vectorDB, - OpenAiKey: !!process.env.OPEN_AI_KEY, - OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo", AuthToken: !!process.env.AUTH_TOKEN, JWTSecret: !!process.env.JWT_SECRET, StorageDir: process.env.STORAGE_DIR, MultiUserMode: await SystemSettings.isMultiUserMode(), + VectorDB: vectorDB, ...(vectorDB === "pinecone" ? { PineConeEnvironment: process.env.PINECONE_ENVIRONMENT, @@ -61,6 +60,22 @@ function systemEndpoints(app) { ChromaEndpoint: process.env.CHROMA_ENDPOINT, } : {}), + LLMProvider: llmProvider, + ...(llmProvider === "openai" + ? { + OpenAiKey: !!process.env.OPEN_AI_KEY, + OpenAiModelPref: process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo", + } + : {}), + + ...(llmProvider === "azure" + ? { + AzureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT, + AzureOpenAiKey: !!process.env.AZURE_OPENAI_KEY, + AzureOpenAiModelPref: process.env.OPEN_MODEL_PREF, + AzureOpenAiEmbeddingModelPref: process.env.EMBEDDING_MODEL_PREF, + } + : {}), }; response.status(200).json({ results }); } catch (e) { diff --git a/server/package.json b/server/package.json index 24b84210a..3d8ec2b80 100644 --- a/server/package.json +++ b/server/package.json @@ -15,6 +15,7 @@ "lint": "yarn prettier --write ./endpoints ./models ./utils index.js" }, "dependencies": { + "@azure/openai": "^1.0.0-beta.3", "@googleapis/youtube": "^9.0.0", "@pinecone-database/pinecone": "^0.1.6", "archiver": "^5.3.1", @@ -43,4 +44,4 @@ "nodemon": "^2.0.22", "prettier": "^2.4.1" } -} \ No newline at end of file +} diff --git a/server/utils/AiProviders/azureOpenAi/index.js b/server/utils/AiProviders/azureOpenAi/index.js new file mode 100644 index 000000000..8743cc8aa --- /dev/null +++ b/server/utils/AiProviders/azureOpenAi/index.js @@ -0,0 +1,99 @@ +class AzureOpenAi { + constructor() { + const { OpenAIClient, AzureKeyCredential } = require("@azure/openai"); + const openai = new OpenAIClient( + process.env.AZURE_OPENAI_ENDPOINT, + new AzureKeyCredential(process.env.AZURE_OPENAI_KEY) + ); + this.openai = openai; + } + + isValidChatModel(_modelName = "") { + // The Azure user names their "models" as deployments and they can be any name + // so we rely on the user to put in the correct deployment as only they would + // know it. + return true; + } + + async isSafe(_input = "") { + // Not implemented by Azure OpenAI so must be stubbed + return { safe: true, reasons: [] }; + } + + async sendChat(chatHistory = [], prompt, workspace = {}) { + const model = process.env.OPEN_MODEL_PREF; + if (!model) + throw new Error( + "No OPEN_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an LLM chat model like GPT-3.5." + ); + + const textResponse = await this.openai + .getChatCompletions( + model, + [ + { role: "system", content: "" }, + ...chatHistory, + { role: "user", content: prompt }, + ], + { + temperature: Number(workspace?.openAiTemp ?? 0.7), + n: 1, + } + ) + .then((res) => { + if (!res.hasOwnProperty("choices")) + throw new Error("OpenAI chat: No results!"); + if (res.choices.length === 0) + throw new Error("OpenAI chat: No results length!"); + return res.choices[0].message.content; + }) + .catch((error) => { + console.log(error); + throw new Error( + `AzureOpenAI::getChatCompletions failed with: ${error.message}` + ); + }); + return textResponse; + } + + async getChatCompletion(messages = [], { temperature = 0.7 }) { + const model = process.env.OPEN_MODEL_PREF; + if (!model) + throw new Error( + "No OPEN_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an LLM chat model like GPT-3.5." + ); + + const data = await this.openai.getChatCompletions(model, messages, { + temperature, + }); + if (!data.hasOwnProperty("choices")) return null; + return data.choices[0].message.content; + } + + async embedTextInput(textInput) { + const result = await this.embedChunks(textInput); + return result?.[0] || []; + } + + async embedChunks(textChunks = []) { + const textEmbeddingModel = + process.env.EMBEDDING_MODEL_PREF || "text-embedding-ada-002"; + if (!textEmbeddingModel) + throw new Error( + "No EMBEDDING_MODEL_PREF ENV defined. This must the name of a deployment on your Azure account for an embedding model." + ); + + const { data = [] } = await this.openai.getEmbeddings( + textEmbeddingModel, + textChunks + ); + return data.length > 0 && + data.every((embd) => embd.hasOwnProperty("embedding")) + ? data.map((embd) => embd.embedding) + : null; + } +} + +module.exports = { + AzureOpenAi, +}; diff --git a/server/utils/AiProviders/openAi/index.js b/server/utils/AiProviders/openAi/index.js index 64b64bdcd..bacc56da0 100644 --- a/server/utils/AiProviders/openAi/index.js +++ b/server/utils/AiProviders/openAi/index.js @@ -1,7 +1,6 @@ -const { Configuration, OpenAIApi } = require("openai"); - class OpenAi { constructor() { + const { Configuration, OpenAIApi } = require("openai"); const config = new Configuration({ apiKey: process.env.OPEN_AI_KEY, }); diff --git a/server/utils/chats/index.js b/server/utils/chats/index.js index b6f74c158..a3a96bc28 100644 --- a/server/utils/chats/index.js +++ b/server/utils/chats/index.js @@ -3,7 +3,8 @@ const { OpenAi } = require("../AiProviders/openAi"); const { WorkspaceChats } = require("../../models/workspaceChats"); const { resetMemory } = require("./commands/reset"); const moment = require("moment"); -const { getVectorDbClass } = require("../helpers"); +const { getVectorDbClass, getLLMProvider } = require("../helpers"); +const { AzureOpenAi } = require("../AiProviders/azureOpenAi"); function convertToChatHistory(history = []) { const formattedHistory = []; @@ -66,7 +67,7 @@ async function chatWithWorkspace( user = null ) { const uuid = uuidv4(); - const openai = new OpenAi(); + const LLMConnector = getLLMProvider(); const VectorDb = getVectorDbClass(); const command = grepCommand(message); @@ -74,7 +75,7 @@ async function chatWithWorkspace( return await VALID_COMMANDS[command](workspace, message, uuid, user); } - const { safe, reasons = [] } = await openai.isSafe(message); + const { safe, reasons = [] } = await LLMConnector.isSafe(message); if (!safe) { return { id: uuid, @@ -93,7 +94,11 @@ async function chatWithWorkspace( if (!hasVectorizedSpace || embeddingsCount === 0) { const rawHistory = await WorkspaceChats.forWorkspace(workspace.id); const chatHistory = convertToPromptHistory(rawHistory); - const response = await openai.sendChat(chatHistory, message, workspace); + const response = await LLMConnector.sendChat( + chatHistory, + message, + workspace + ); const data = { text: response, sources: [], type: "chat" }; await WorkspaceChats.new({ diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index 1a5aac863..5be565074 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -1,21 +1,34 @@ function getVectorDbClass() { - const { Pinecone } = require("../vectorDbProviders/pinecone"); - const { Chroma } = require("../vectorDbProviders/chroma"); - const { LanceDb } = require("../vectorDbProviders/lance"); - const vectorSelection = process.env.VECTOR_DB || "pinecone"; switch (vectorSelection) { case "pinecone": + const { Pinecone } = require("../vectorDbProviders/pinecone"); return Pinecone; case "chroma": + const { Chroma } = require("../vectorDbProviders/chroma"); return Chroma; case "lancedb": + const { LanceDb } = require("../vectorDbProviders/lance"); return LanceDb; default: throw new Error("ENV: No VECTOR_DB value found in environment!"); } } +function getLLMProvider() { + const vectorSelection = process.env.LLM_PROVIDER || "openai"; + switch (vectorSelection) { + case "openai": + const { OpenAi } = require("../AiProviders/openAi"); + return new OpenAi(); + case "azure": + const { AzureOpenAi } = require("../AiProviders/azureOpenAi"); + return new AzureOpenAi(); + default: + throw new Error("ENV: No LLM_PROVIDER 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) @@ -24,5 +37,6 @@ function toChunks(arr, size) { module.exports = { getVectorDbClass, + getLLMProvider, toChunks, }; diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 0ff95fa49..64c919884 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -1,4 +1,9 @@ const KEY_MAPPING = { + LLMProvider: { + envKey: "LLM_PROVIDER", + checks: [isNotEmpty, supportedLLM], + }, + // OpenAI Settings OpenAiKey: { envKey: "OPEN_AI_KEY", checks: [isNotEmpty, validOpenAIKey], @@ -7,6 +12,25 @@ const KEY_MAPPING = { envKey: "OPEN_MODEL_PREF", checks: [isNotEmpty, validOpenAIModel], }, + // Azure OpenAI Settings + AzureOpenAiEndpoint: { + envKey: "AZURE_OPENAI_ENDPOINT", + checks: [isNotEmpty, validAzureURL], + }, + AzureOpenAiKey: { + envKey: "AZURE_OPENAI_KEY", + checks: [isNotEmpty], + }, + AzureOpenAiModelPref: { + envKey: "OPEN_MODEL_PREF", + checks: [isNotEmpty], + }, + AzureOpenAiEmbeddingModelPref: { + envKey: "EMBEDDING_MODEL_PREF", + checks: [isNotEmpty], + }, + + // Vector Database Selection Settings VectorDB: { envKey: "VECTOR_DB", checks: [isNotEmpty, supportedVectorDB], @@ -27,6 +51,8 @@ const KEY_MAPPING = { envKey: "PINECONE_INDEX", checks: [], }, + + // System Settings AuthToken: { envKey: "AUTH_TOKEN", checks: [], @@ -56,6 +82,10 @@ function validOpenAIKey(input = "") { return input.startsWith("sk-") ? null : "OpenAI Key must start with sk-"; } +function supportedLLM(input = "") { + return ["openai", "azure"].includes(input); +} + function validOpenAIModel(input = "") { const validModels = [ "gpt-4", @@ -85,6 +115,17 @@ function validChromaURL(input = "") { : null; } +function validAzureURL(input = "") { + try { + new URL(input); + if (!input.includes("openai.azure.com")) + return "URL must include openai.azure.com"; + return null; + } catch { + return "Not a valid URL"; + } +} + // This will force update .env variables which for any which reason were not able to be parsed or // read from an ENV file as this seems to be a complicating step for many so allowing people to write // to the process will at least alleviate that issue. It does not perform comprehensive validity checks or sanity checks diff --git a/server/utils/vectorDbProviders/chroma/index.js b/server/utils/vectorDbProviders/chroma/index.js index ee8ac4850..4a527ac72 100644 --- a/server/utils/vectorDbProviders/chroma/index.js +++ b/server/utils/vectorDbProviders/chroma/index.js @@ -1,14 +1,9 @@ -const { ChromaClient, OpenAIEmbeddingFunction } = require("chromadb"); -const { Chroma: ChromaStore } = require("langchain/vectorstores/chroma"); -const { OpenAI } = require("langchain/llms/openai"); -const { VectorDBQAChain } = require("langchain/chains"); -const { OpenAIEmbeddings } = require("langchain/embeddings/openai"); +const { ChromaClient } = require("chromadb"); const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter"); const { storeVectorResult, cachedVectorInformation } = require("../../files"); const { v4: uuidv4 } = require("uuid"); -const { toChunks } = require("../../helpers"); +const { toChunks, getLLMProvider } = require("../../helpers"); const { chatPrompt } = require("../../chats"); -const { OpenAi } = require("../../AiProviders/openAi"); const Chroma = { name: "Chroma", @@ -49,22 +44,6 @@ const Chroma = { const namespace = await this.namespace(client, _namespace); return namespace?.vectorCount || 0; }, - embeddingFunc: function () { - return new OpenAIEmbeddingFunction({ - openai_api_key: process.env.OPEN_AI_KEY, - }); - }, - embedder: function () { - return new OpenAIEmbeddings({ openAIApiKey: process.env.OPEN_AI_KEY }); - }, - llm: function ({ temperature = 0.7 }) { - const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo"; - return new OpenAI({ - openAIApiKey: process.env.OPEN_AI_KEY, - modelName: model, - temperature, - }); - }, similarityResponse: async function (client, namespace, queryVector) { const collection = await client.getCollection({ name: namespace }); const result = { @@ -131,7 +110,6 @@ const Chroma = { const collection = await client.getOrCreateCollection({ name: namespace, metadata: { "hnsw:space": "cosine" }, - embeddingFunction: this.embeddingFunc(), }); const { chunks } = cacheResult; const documentVectors = []; @@ -176,10 +154,10 @@ const Chroma = { const textChunks = await textSplitter.splitText(pageContent); console.log("Chunks created from document:", textChunks.length); - const openAiConnector = new OpenAi(); + const LLMConnector = getLLMProvider(); const documentVectors = []; const vectors = []; - const vectorValues = await openAiConnector.embedChunks(textChunks); + const vectorValues = await LLMConnector.embedChunks(textChunks); const submission = { ids: [], embeddings: [], @@ -216,7 +194,6 @@ const Chroma = { const collection = await client.getOrCreateCollection({ name: namespace, metadata: { "hnsw:space": "cosine" }, - embeddingFunction: this.embeddingFunc(), }); if (vectors.length > 0) { @@ -245,7 +222,6 @@ const Chroma = { if (!(await this.namespaceExists(client, namespace))) return; const collection = await client.getCollection({ name: namespace, - embeddingFunction: this.embeddingFunc(), }); const knownDocuments = await DocumentVectors.where(`docId = '${docId}'`); @@ -271,22 +247,36 @@ const Chroma = { }; } - const vectorStore = await ChromaStore.fromExistingCollection( - this.embedder(), - { collectionName: namespace, url: process.env.CHROMA_ENDPOINT } + const LLMConnector = getLLMProvider(); + const queryVector = await LLMConnector.embedTextInput(input); + const { contextTexts, sourceDocuments } = await this.similarityResponse( + client, + namespace, + queryVector ); - const model = this.llm({ + 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, }); - const chain = VectorDBQAChain.fromLLM(model, vectorStore, { - k: 5, - returnSourceDocuments: true, + // When we roll out own response we have separate metadata and texts, + // so for source collection we need to combine them. + const sources = sourceDocuments.map((metadata, i) => { + return { metadata: { ...metadata, text: contextTexts[i] } }; }); - const response = await chain.call({ query: input }); return { - response: response.text, - sources: this.curateSources(response.sourceDocuments), + response: responseText, + sources: this.curateSources(sources), message: false, }; }, @@ -312,8 +302,8 @@ const Chroma = { }; } - const openAiConnector = new OpenAi(); - const queryVector = await openAiConnector.embedTextInput(input); + const LLMConnector = getLLMProvider(); + const queryVector = await LLMConnector.embedTextInput(input); const { contextTexts, sourceDocuments } = await this.similarityResponse( client, namespace, @@ -330,7 +320,7 @@ const Chroma = { .join("")}`, }; const memory = [prompt, ...chatHistory, { role: "user", content: input }]; - const responseText = await openAiConnector.getChatCompletion(memory, { + const responseText = await LLMConnector.getChatCompletion(memory, { temperature: workspace?.openAiTemp ?? 0.7, }); diff --git a/server/utils/vectorDbProviders/lance/index.js b/server/utils/vectorDbProviders/lance/index.js index 38b30af92..aeb33534b 100644 --- a/server/utils/vectorDbProviders/lance/index.js +++ b/server/utils/vectorDbProviders/lance/index.js @@ -1,11 +1,10 @@ const lancedb = require("vectordb"); -const { toChunks } = require("../../helpers"); +const { toChunks, getLLMProvider } = require("../../helpers"); const { OpenAIEmbeddings } = require("langchain/embeddings/openai"); const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter"); const { storeVectorResult, cachedVectorInformation } = require("../../files"); const { v4: uuidv4 } = require("uuid"); const { chatPrompt } = require("../../chats"); -const { OpenAi } = require("../../AiProviders/openAi"); const LanceDb = { uri: `${ @@ -169,11 +168,11 @@ const LanceDb = { const textChunks = await textSplitter.splitText(pageContent); console.log("Chunks created from document:", textChunks.length); - const openAiConnector = new OpenAi(); + const LLMConnector = getLLMProvider(); const documentVectors = []; const vectors = []; const submissions = []; - const vectorValues = await openAiConnector.embedChunks(textChunks); + const vectorValues = await LLMConnector.embedChunks(textChunks); if (!!vectorValues && vectorValues.length > 0) { for (const [i, vector] of vectorValues.entries()) { @@ -230,9 +229,8 @@ const LanceDb = { }; } - // LanceDB does not have langchainJS support so we roll our own here. - const openAiConnector = new OpenAi(); - const queryVector = await openAiConnector.embedTextInput(input); + const LLMConnector = getLLMProvider(); + const queryVector = await LLMConnector.embedTextInput(input); const { contextTexts, sourceDocuments } = await this.similarityResponse( client, namespace, @@ -249,7 +247,7 @@ const LanceDb = { .join("")}`, }; const memory = [prompt, { role: "user", content: input }]; - const responseText = await openAiConnector.getChatCompletion(memory, { + const responseText = await LLMConnector.getChatCompletion(memory, { temperature: workspace?.openAiTemp ?? 0.7, }); @@ -281,8 +279,8 @@ const LanceDb = { }; } - const openAiConnector = new OpenAi(); - const queryVector = await openAiConnector.embedTextInput(input); + const LLMConnector = getLLMProvider(); + const queryVector = await LLMConnector.embedTextInput(input); const { contextTexts, sourceDocuments } = await this.similarityResponse( client, namespace, @@ -299,7 +297,7 @@ const LanceDb = { .join("")}`, }; const memory = [prompt, ...chatHistory, { role: "user", content: input }]; - const responseText = await openAiConnector.getChatCompletion(memory, { + const responseText = await LLMConnector.getChatCompletion(memory, { temperature: workspace?.openAiTemp ?? 0.7, }); diff --git a/server/utils/vectorDbProviders/pinecone/index.js b/server/utils/vectorDbProviders/pinecone/index.js index 6021cfa37..91d97578f 100644 --- a/server/utils/vectorDbProviders/pinecone/index.js +++ b/server/utils/vectorDbProviders/pinecone/index.js @@ -1,14 +1,9 @@ const { PineconeClient } = require("@pinecone-database/pinecone"); -const { PineconeStore } = require("langchain/vectorstores/pinecone"); -const { OpenAI } = require("langchain/llms/openai"); -const { VectorDBQAChain } = require("langchain/chains"); -const { OpenAIEmbeddings } = require("langchain/embeddings/openai"); const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter"); const { storeVectorResult, cachedVectorInformation } = require("../../files"); const { v4: uuidv4 } = require("uuid"); -const { toChunks } = require("../../helpers"); +const { toChunks, getLLMProvider } = require("../../helpers"); const { chatPrompt } = require("../../chats"); -const { OpenAi } = require("../../AiProviders/openAi"); const Pinecone = { name: "Pinecone", @@ -29,17 +24,6 @@ const Pinecone = { if (!status.ready) throw new Error("Pinecode::Index not ready."); return { client, pineconeIndex, indexName: process.env.PINECONE_INDEX }; }, - embedder: function () { - return new OpenAIEmbeddings({ openAIApiKey: process.env.OPEN_AI_KEY }); - }, - llm: function ({ temperature = 0.7 }) { - const model = process.env.OPEN_MODEL_PREF || "gpt-3.5-turbo"; - return new OpenAI({ - openAIApiKey: process.env.OPEN_AI_KEY, - modelName: model, - temperature, - }); - }, totalIndicies: async function () { const { pineconeIndex } = await this.connect(); const { namespaces } = await pineconeIndex.describeIndexStats1(); @@ -144,10 +128,10 @@ const Pinecone = { const textChunks = await textSplitter.splitText(pageContent); console.log("Chunks created from document:", textChunks.length); - const openAiConnector = new OpenAi(); + const LLMConnector = getLLMProvider(); const documentVectors = []; const vectors = []; - const vectorValues = await openAiConnector.embedChunks(textChunks); + const vectorValues = await LLMConnector.embedChunks(textChunks); if (!!vectorValues && vectorValues.length > 0) { for (const [i, vector] of vectorValues.entries()) { @@ -246,22 +230,32 @@ const Pinecone = { }; } - const vectorStore = await PineconeStore.fromExistingIndex(this.embedder(), { + const LLMConnector = getLLMProvider(); + const queryVector = await LLMConnector.embedTextInput(input); + const { contextTexts, sourceDocuments } = await this.similarityResponse( pineconeIndex, 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 model = this.llm({ + const memory = [prompt, { role: "user", content: input }]; + const responseText = await LLMConnector.getChatCompletion(memory, { temperature: workspace?.openAiTemp ?? 0.7, }); - const chain = VectorDBQAChain.fromLLM(model, vectorStore, { - k: 5, - returnSourceDocuments: true, - }); - const response = await chain.call({ query: input }); + return { - response: response.text, - sources: this.curateSources(response.sourceDocuments), + response: responseText, + sources: this.curateSources(sourceDocuments), message: false, }; }, @@ -284,8 +278,8 @@ const Pinecone = { "Invalid namespace - has it been collected and seeded yet?" ); - const openAiConnector = new OpenAi(); - const queryVector = await openAiConnector.embedTextInput(input); + const LLMConnector = getLLMProvider(); + const queryVector = await LLMConnector.embedTextInput(input); const { contextTexts, sourceDocuments } = await this.similarityResponse( pineconeIndex, namespace, @@ -303,7 +297,7 @@ const Pinecone = { }; const memory = [prompt, ...chatHistory, { role: "user", content: input }]; - const responseText = await openAiConnector.getChatCompletion(memory, { + const responseText = await LLMConnector.getChatCompletion(memory, { temperature: workspace?.openAiTemp ?? 0.7, }); diff --git a/server/yarn.lock b/server/yarn.lock index 1a82497ed..cd1514e7a 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -26,6 +26,93 @@ pad-left "^2.1.0" tslib "^2.5.0" +"@azure-rest/core-client@^1.1.3": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@azure-rest/core-client/-/core-client-1.1.4.tgz#628381c3653f6dbae584ca6f2ae5f74a5c015526" + integrity sha512-RUIQOA8T0WcbNlddr8hjl2MuC5GVRqmMwPXqBVsgvdKesLy+eg3y/6nf3qe2fvcJMI1gF6VtgU5U4hRaR4w4ag== + dependencies: + "@azure/abort-controller" "^1.1.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-rest-pipeline" "^1.5.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.0.0" + tslib "^2.2.0" + +"@azure/abort-controller@^1.0.0", "@azure/abort-controller@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== + dependencies: + tslib "^2.2.0" + +"@azure/core-auth@^1.3.0", "@azure/core-auth@^1.4.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.5.0.tgz#a41848c5c31cb3b7c84c409885267d55a2c92e44" + integrity sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-util" "^1.1.0" + tslib "^2.2.0" + +"@azure/core-lro@^2.5.3": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.5.4.tgz#b21e2bcb8bd9a8a652ff85b61adeea51a8055f90" + integrity sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-util" "^1.2.0" + "@azure/logger" "^1.0.0" + tslib "^2.2.0" + +"@azure/core-rest-pipeline@^1.10.2", "@azure/core-rest-pipeline@^1.5.0": + version "1.12.0" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.0.tgz#a36dd361807494845522824532c076daa27c2786" + integrity sha512-+MnSB0vGZjszSzr5AW8z93/9fkDu2RLtWmAN8gskURq7EW2sSwqy8jZa0V26rjuBVkwhdA3Hw8z3VWoeBUOw+A== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.3.0" + "@azure/logger" "^1.0.0" + form-data "^4.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" + +"@azure/core-tracing@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503" + integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw== + dependencies: + tslib "^2.2.0" + +"@azure/core-util@^1.0.0", "@azure/core-util@^1.1.0", "@azure/core-util@^1.2.0", "@azure/core-util@^1.3.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.4.0.tgz#c120a56b3e48a9e4d20619a0b00268ae9de891c7" + integrity sha512-eGAyJpm3skVQoLiRqm/xPa+SXi/NPDdSHMxbRAz2lSprd+Zs+qrpQGQQ2VQ3Nttu+nSZR4XoYQC71LbEI7jsig== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0", "@azure/logger@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.4.tgz#28bc6d0e5b3c38ef29296b32d35da4e483593fa1" + integrity sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg== + dependencies: + tslib "^2.2.0" + +"@azure/openai@^1.0.0-beta.3": + version "1.0.0-beta.3" + resolved "https://registry.yarnpkg.com/@azure/openai/-/openai-1.0.0-beta.3.tgz#bf4f5ec0a5644b3a9ce4372620856a65e7721e24" + integrity sha512-gW4odbuy/X/W34SdvXomj/JzR09MyMHCY5Kd2ZxJkQo3IUGqJXz1rEv6QER7IAGgBFgNawE97K6UuJfMmoT0rw== + dependencies: + "@azure-rest/core-client" "^1.1.3" + "@azure/core-auth" "^1.4.0" + "@azure/core-lro" "^2.5.3" + "@azure/core-rest-pipeline" "^1.10.2" + "@azure/logger" "^1.0.3" + tslib "^2.4.0" + "@fortaine/fetch-event-source@^3.0.6": version "3.0.6" resolved "https://registry.yarnpkg.com/@fortaine/fetch-event-source/-/fetch-event-source-3.0.6.tgz#b8552a2ca2c5202f5699b93a92be0188d422b06e" @@ -86,6 +173,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@types/command-line-args@5.2.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.2.0.tgz#adbb77980a1cc376bb208e3f4142e907410430f6" @@ -1128,6 +1220,15 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -2301,6 +2402,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tslib@^2.2.0, tslib@^2.4.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.1.tgz#fd8c9a0ff42590b25703c0acb3de3d3f4ede0410" + integrity sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig== + tslib@^2.5.0: version "2.6.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" -- GitLab