diff --git a/frontend/src/components/Sidebar/index.jsx b/frontend/src/components/Sidebar/index.jsx index f0c27f09dd566e206530840705c9b3a1c84e0957..95f232d7a69806e7d0e2f8d1052f2a665097aeab 100644 --- a/frontend/src/components/Sidebar/index.jsx +++ b/frontend/src/components/Sidebar/index.jsx @@ -278,7 +278,7 @@ function SettingsButton() { return ( <a href={ - !!user?.role ? paths.settings.system() : paths.settings.appearance() + !!user?.role ? paths.settings.system() : paths.settings.appearance() } className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border" > diff --git a/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx b/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx index f35c167b5ccfba82f5b13a7dceb967ae63f12726..cc272edb3ac95cc231d4f72aa3fbf02dffa18523 100644 --- a/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx +++ b/frontend/src/pages/GeneralSettings/EmbeddingPreference/index.jsx @@ -133,161 +133,161 @@ export default function GeneralEmbeddingPreference() { </p> </div> - {["openai", "azure"].includes(settings.LLMProvider) ? ( - <div className="w-full h-20 items-center justify-center flex"> - <p className="text-gray-800 dark:text-slate-400 text-center"> - Your current LLM preference does not require you to set up - this part of AnythingLLM. - <br /> - Embedding is being automatically managed by AnythingLLM. - </p> + <> + <div className="text-white text-sm font-medium py-4"> + Embedding Providers + </div> + <div className="w-full flex md:flex-wrap overflow-x-scroll gap-4 max-w-[900px]"> + <input + hidden={true} + name="EmbeddingEngine" + value={embeddingChoice} + /> + <LLMProviderOption + name="OpenAI" + value="openai" + link="openai.com" + description="Use OpenAI's text-embedding-ada-002 embedding model." + checked={embeddingChoice === "openai"} + image={OpenAiLogo} + onClick={updateChoice} + /> + <LLMProviderOption + name="Azure OpenAI" + value="azure" + link="azure.microsoft.com" + description="The enterprise option of OpenAI hosted on Azure services." + checked={embeddingChoice === "azure"} + image={AzureOpenAiLogo} + onClick={updateChoice} + /> + <LLMProviderOption + name="LocalAI" + value="localai" + link="localai.io" + description="Self hosted LocalAI embedding engine." + checked={embeddingChoice === "localai"} + image={LocalAiLogo} + onClick={updateChoice} + /> </div> - ) : ( - <> - <div className="text-white text-sm font-medium py-4"> - Embedding Providers - </div> - <div className="w-full flex md:flex-wrap overflow-x-scroll gap-4 max-w-[900px]"> - <input - hidden={true} - name="EmbeddingEngine" - value={embeddingChoice} - /> - <LLMProviderOption - name="OpenAI" - value="openai" - link="openai.com" - description="Use OpenAI's text-embedding-ada-002 embedding model." - checked={embeddingChoice === "openai"} - image={OpenAiLogo} - onClick={updateChoice} - /> - <LLMProviderOption - name="Azure OpenAI" - value="azure" - link="azure.microsoft.com" - description="The enterprise option of OpenAI hosted on Azure services." - checked={embeddingChoice === "azure"} - image={AzureOpenAiLogo} - onClick={updateChoice} - /> - <LLMProviderOption - name="LocalAI" - value="localai" - link="localai.io" - description="Self hosted LocalAI embedding engine." - checked={embeddingChoice === "localai"} - image={LocalAiLogo} - onClick={updateChoice} - /> - </div> - <div className="mt-10 flex flex-wrap gap-4 max-w-[800px]"> - {embeddingChoice === "openai" && ( - <> - <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - API Key - </label> - <input - type="text" - name="OpenAiKey" - className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="OpenAI API Key" - defaultValue={ - settings?.OpenAiKey ? "*".repeat(20) : "" - } - required={true} - autoComplete="off" - spellCheck={false} - /> - </div> - </> - )} + <div className="mt-10 flex flex-wrap gap-4 max-w-[800px]"> + {embeddingChoice === "openai" && ( + <> + <div className="flex flex-col w-60"> + <label className="text-white text-sm font-semibold block mb-4"> + API Key + </label> + <input + type="text" + name="OpenAiKey" + className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" + placeholder="OpenAI API Key" + defaultValue={ + settings?.OpenAiKey ? "*".repeat(20) : "" + } + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> + <div className="flex flex-col w-60"> + <label className="text-white text-sm font-semibold block mb-4"> + Model Preference + </label> + <select + disabled={true} + className="cursor-not-allowed bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" + > + <option disabled={true} selected={true}> + text-embedding-ada-002 + </option> + </select> + </div> + </> + )} - {embeddingChoice === "azure" && ( - <> - <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Azure Service Endpoint - </label> - <input - type="url" - name="AzureOpenAiEndpoint" - className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="https://my-azure.openai.azure.com" - defaultValue={settings?.AzureOpenAiEndpoint} - required={true} - autoComplete="off" - spellCheck={false} - /> - </div> + {embeddingChoice === "azure" && ( + <> + <div className="flex flex-col w-60"> + <label className="text-white text-sm font-semibold block mb-4"> + Azure Service Endpoint + </label> + <input + type="url" + name="AzureOpenAiEndpoint" + className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" + placeholder="https://my-azure.openai.azure.com" + defaultValue={settings?.AzureOpenAiEndpoint} + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> - <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - API Key - </label> - <input - type="password" - name="AzureOpenAiKey" - className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="Azure OpenAI API Key" - defaultValue={ - settings?.AzureOpenAiKey ? "*".repeat(20) : "" - } - required={true} - autoComplete="off" - spellCheck={false} - /> - </div> + <div className="flex flex-col w-60"> + <label className="text-white text-sm font-semibold block mb-4"> + API Key + </label> + <input + type="password" + name="AzureOpenAiKey" + className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" + placeholder="Azure OpenAI API Key" + defaultValue={ + settings?.AzureOpenAiKey ? "*".repeat(20) : "" + } + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> - <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - Embedding Deployment Name - </label> - <input - type="text" - name="AzureOpenAiEmbeddingModelPref" - className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="Azure OpenAI embedding model deployment name" - defaultValue={ - settings?.AzureOpenAiEmbeddingModelPref - } - required={true} - autoComplete="off" - spellCheck={false} - /> - </div> - </> - )} + <div className="flex flex-col w-60"> + <label className="text-white text-sm font-semibold block mb-4"> + Embedding Deployment Name + </label> + <input + type="text" + name="AzureOpenAiEmbeddingModelPref" + className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" + placeholder="Azure OpenAI embedding model deployment name" + defaultValue={settings?.AzureOpenAiEmbeddingModelPref} + required={true} + autoComplete="off" + spellCheck={false} + /> + </div> + </> + )} - {embeddingChoice === "localai" && ( - <> - <div className="flex flex-col w-60"> - <label className="text-white text-sm font-semibold block mb-4"> - LocalAI Base URL - </label> - <input - type="url" - name="EmbeddingBasePath" - className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" - placeholder="http://localhost:8080/v1" - defaultValue={settings?.EmbeddingBasePath} - onChange={(e) => setBasePathValue(e.target.value)} - onBlur={updateBasePath} - required={true} - autoComplete="off" - spellCheck={false} - /> - </div> - <LocalAIModelSelection - settings={settings} - basePath={basePath} + {embeddingChoice === "localai" && ( + <> + <div className="flex flex-col w-60"> + <label className="text-white text-sm font-semibold block mb-4"> + LocalAI Base URL + </label> + <input + type="url" + name="EmbeddingBasePath" + className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5" + placeholder="http://localhost:8080/v1" + defaultValue={settings?.EmbeddingBasePath} + onChange={(e) => setBasePathValue(e.target.value)} + onBlur={updateBasePath} + required={true} + autoComplete="off" + spellCheck={false} /> - </> - )} - </div> - </> - )} + </div> + <LocalAIModelSelection + settings={settings} + basePath={basePath} + /> + </> + )} + </div> + </> </div> </form> </div> diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx index 2f133ff5b7e85a353d0a1bd434f9cf163e387e24..b81a6795e07940c275f829a96bd7ee49b3f32466 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx @@ -97,16 +97,44 @@ const VECTOR_DB_PRIVACY = { }, }; +const EMBEDDING_ENGINE_PRIVACY = { + openai: { + name: "OpenAI", + description: [ + "Your documents are visible to OpenAI", + "Your documents are not used for training", + ], + logo: OpenAiLogo, + }, + azure: { + name: "Azure OpenAI", + description: [ + "Your documents are not visible to OpenAI or Microsoft", + "Your documents not used for training", + ], + logo: AzureOpenAiLogo, + }, + localai: { + name: "LocalAI", + description: [ + "Your documents are only accessible on the server running LocalAI", + ], + logo: LocalAiLogo, + }, +}; + function DataHandling({ nextStep, prevStep, currentStep }) { const [llmChoice, setLLMChoice] = useState("openai"); const [loading, setLoading] = useState(true); const [vectorDb, setVectorDb] = useState("pinecone"); + const [embeddingEngine, setEmbeddingEngine] = useState("openai"); useEffect(() => { async function fetchKeys() { const _settings = await System.keys(); setLLMChoice(_settings?.LLMProvider); setVectorDb(_settings?.VectorDB); + setEmbeddingEngine(_settings?.EmbeddingEngine); setLoading(false); } @@ -124,8 +152,8 @@ function DataHandling({ nextStep, prevStep, currentStep }) { return ( <div className="max-w-[750px]"> - <div className="p-8 flex gap-x-16"> - <div className="w-1/2 flex flex-col gap-y-3.5"> + <div className="p-8 flex flex-col gap-8"> + <div className="flex flex-col gap-y-3.5 border-b border-zinc-500/50 pb-8"> <div className="text-white text-base font-bold">LLM Selection</div> <div className="flex items-center gap-2.5"> <img @@ -146,7 +174,28 @@ function DataHandling({ nextStep, prevStep, currentStep }) { </div> </div> - <div className="w-1/2 flex flex-col gap-y-3.5"> + <div className="flex flex-col gap-y-3.5 border-b border-zinc-500/50 pb-8"> + <div className="text-white text-base font-bold">Embedding Engine</div> + <div className="flex items-center gap-2.5"> + <img + src={EMBEDDING_ENGINE_PRIVACY[embeddingEngine].logo} + alt="Vector DB Logo" + className="w-8 h-8 rounded" + /> + <p className="text-white text-sm font-bold"> + {EMBEDDING_ENGINE_PRIVACY[embeddingEngine].name} + </p> + </div> + <ul className="flex flex-col list-disc"> + {EMBEDDING_ENGINE_PRIVACY[embeddingEngine].description.map( + (desc) => ( + <li className="text-white/90 text-sm">{desc}</li> + ) + )} + </ul> + </div> + + <div className="flex flex-col gap-y-3.5 "> <div className="text-white text-base font-bold">Vector Database</div> <div className="flex items-center gap-2.5"> <img diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/EmbeddingSelection/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/EmbeddingSelection/index.jsx index 109e4115f0c58102ac9df473c0eaad121bfa0c0b..da90cc4f2f1cc6b78bd4623d1a9ba402ca0153c1 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/EmbeddingSelection/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/EmbeddingSelection/index.jsx @@ -113,6 +113,19 @@ function EmbeddingSelection({ nextStep, prevStep, currentStep }) { spellCheck={false} /> </div> + <div className="flex flex-col w-60"> + <label className="text-white text-sm font-semibold block mb-4"> + Model Preference + </label> + <select + disabled={true} + className="cursor-not-allowed bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" + > + <option disabled={true} selected={true}> + text-embedding-ada-002 + </option> + </select> + </div> </> )} diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx index e3582309021bacc6919489ed478339078abf5b0a..73e68c05cbb1d25975bda96f53f5b1f4bd303389 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx @@ -46,15 +46,7 @@ function LLMSelection({ nextStep, prevStep, currentStep }) { alert(`Failed to save LLM settings: ${error}`, "error"); return; } - - switch (data.LLMProvider) { - case "anthropic": - case "lmstudio": - case "localai": - return nextStep("embedding_preferences"); - default: - return nextStep("vector_database"); - } + nextStep("embedding_preferences"); }; if (loading) diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx index c4dceed874e340410402458479802a8b2e79d83f..d00b7e95664d56eadea1d99a3a0cb5b03a3a6771 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx @@ -65,8 +65,7 @@ const STEPS = { }, embedding_preferences: { title: "Embedding Preference", - description: - "Due to your LLM selection you need to set up a provider for embedding files and text.", + description: "Choose a provider for embedding files and text.", component: EmbeddingSelection, }, }; diff --git a/server/utils/AiProviders/azureOpenAi/index.js b/server/utils/AiProviders/azureOpenAi/index.js index a424902b1667c9b0eef4f869603aebdee6621173..82e28204bca631e36bcb8499d8906d0bb96d2515 100644 --- a/server/utils/AiProviders/azureOpenAi/index.js +++ b/server/utils/AiProviders/azureOpenAi/index.js @@ -1,9 +1,8 @@ const { AzureOpenAiEmbedder } = require("../../EmbeddingEngines/azureOpenAi"); const { chatPrompt } = require("../../chats"); -class AzureOpenAiLLM extends AzureOpenAiEmbedder { - constructor() { - super(); +class AzureOpenAiLLM { + constructor(embedder = null) { const { OpenAIClient, AzureKeyCredential } = require("@azure/openai"); if (!process.env.AZURE_OPENAI_ENDPOINT) throw new Error("No Azure API endpoint was set."); @@ -20,6 +19,12 @@ class AzureOpenAiLLM extends AzureOpenAiEmbedder { system: this.promptWindowLimit() * 0.15, user: this.promptWindowLimit() * 0.7, }; + + if (!embedder) + console.warn( + "No embedding provider defined for AzureOpenAiLLM - falling back to AzureOpenAiEmbedder for embedding!" + ); + this.embedder = !embedder ? new AzureOpenAiEmbedder() : embedder; } streamingEnabled() { @@ -114,6 +119,14 @@ Context: return data.choices[0].message.content; } + // Simple wrapper for dynamic embedder & normalize interface for all LLM implementations + async embedTextInput(textInput) { + return await this.embedder.embedTextInput(textInput); + } + async embedChunks(textChunks = []) { + return await this.embedder.embedChunks(textChunks); + } + async compressMessages(promptArgs = {}, rawHistory = []) { const { messageArrayCompressor } = require("../../helpers/chat"); const messageArray = this.constructPrompt(promptArgs); diff --git a/server/utils/AiProviders/openAi/index.js b/server/utils/AiProviders/openAi/index.js index 0c5b7116dd2b9e367deed2fa5fed878f04fdf689..46464271968244e378b28b1b1d7cf3045eb8cc21 100644 --- a/server/utils/AiProviders/openAi/index.js +++ b/server/utils/AiProviders/openAi/index.js @@ -1,9 +1,8 @@ const { OpenAiEmbedder } = require("../../EmbeddingEngines/openAi"); const { chatPrompt } = require("../../chats"); -class OpenAiLLM extends OpenAiEmbedder { - constructor() { - super(); +class OpenAiLLM { + constructor(embedder = null) { const { Configuration, OpenAIApi } = require("openai"); if (!process.env.OPEN_AI_KEY) throw new Error("No OpenAI API key was set."); @@ -17,6 +16,12 @@ class OpenAiLLM extends OpenAiEmbedder { system: this.promptWindowLimit() * 0.15, user: this.promptWindowLimit() * 0.7, }; + + if (!embedder) + console.warn( + "No embedding provider defined for OpenAiLLM - falling back to OpenAiEmbedder for embedding!" + ); + this.embedder = !embedder ? new OpenAiEmbedder() : embedder; } streamingEnabled() { @@ -203,6 +208,14 @@ Context: return streamRequest; } + // Simple wrapper for dynamic embedder & normalize interface for all LLM implementations + async embedTextInput(textInput) { + return await this.embedder.embedTextInput(textInput); + } + async embedChunks(textChunks = []) { + return await this.embedder.embedChunks(textChunks); + } + async compressMessages(promptArgs = {}, rawHistory = []) { const { messageArrayCompressor } = require("../../helpers/chat"); const messageArray = this.constructPrompt(promptArgs); diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index c7c61822141e0eda615e860d2b35eba129c5b9b1..74804b904239cf8bd72271e9472f536f5351e578 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -23,25 +23,22 @@ function getVectorDbClass() { function getLLMProvider() { const vectorSelection = process.env.LLM_PROVIDER || "openai"; - let embedder = null; + const embedder = getEmbeddingEngineSelection(); switch (vectorSelection) { case "openai": const { OpenAiLLM } = require("../AiProviders/openAi"); - return new OpenAiLLM(); + return new OpenAiLLM(embedder); case "azure": const { AzureOpenAiLLM } = require("../AiProviders/azureOpenAi"); - return new AzureOpenAiLLM(); + return new AzureOpenAiLLM(embedder); case "anthropic": const { AnthropicLLM } = require("../AiProviders/anthropic"); - embedder = getEmbeddingEngineSelection(); return new AnthropicLLM(embedder); case "lmstudio": const { LMStudioLLM } = require("../AiProviders/lmStudio"); - embedder = getEmbeddingEngineSelection(); return new LMStudioLLM(embedder); case "localai": const { LocalAiLLM } = require("../AiProviders/localAi"); - embedder = getEmbeddingEngineSelection(); return new LocalAiLLM(embedder); default: throw new Error("ENV: No LLM_PROVIDER value found in environment!");