From b2dd35fe152e901b06f2666ee5a9f2ae56af0437 Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Thu, 5 Dec 2024 10:38:23 -0800 Subject: [PATCH] Add Support for NVIDIA NIM (#2766) * Add Support for NVIDIA NIM * update README * linting --- README.md | 1 + docker/.env.example | 4 + .../LLMSelection/NvidiaNimOptions/index.jsx | 11 + .../LLMSelection/NvidiaNimOptions/managed.jsx | 7 + .../LLMSelection/NvidiaNimOptions/remote.jsx | 130 +++++++++++ frontend/src/hooks/useGetProvidersModels.js | 1 + frontend/src/media/llmprovider/nvidia-nim.png | Bin 0 -> 65902 bytes .../GeneralSettings/LLMPreference/index.jsx | 11 + .../Steps/DataHandling/index.jsx | 8 + .../Steps/LLMPreference/index.jsx | 11 +- .../AgentConfig/AgentLLMSelection/index.jsx | 1 + frontend/src/utils/constants.js | 7 + server/.env.example | 4 + server/models/systemSettings.js | 5 + server/utils/AiProviders/nvidiaNim/index.js | 220 ++++++++++++++++++ server/utils/agents/aibitat/index.js | 2 + .../agents/aibitat/providers/ai-provider.js | 14 +- .../utils/agents/aibitat/providers/index.js | 2 + .../agents/aibitat/providers/nvidiaNim.js | 117 ++++++++++ server/utils/agents/index.js | 8 + server/utils/helpers/customModels.js | 36 +++ server/utils/helpers/index.js | 6 + server/utils/helpers/updateENV.js | 24 ++ 23 files changed, 626 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/LLMSelection/NvidiaNimOptions/index.jsx create mode 100644 frontend/src/components/LLMSelection/NvidiaNimOptions/managed.jsx create mode 100644 frontend/src/components/LLMSelection/NvidiaNimOptions/remote.jsx create mode 100644 frontend/src/media/llmprovider/nvidia-nim.png create mode 100644 server/utils/AiProviders/nvidiaNim/index.js create mode 100644 server/utils/agents/aibitat/providers/nvidiaNim.js diff --git a/README.md b/README.md index b8202ecd0f..90adb0430f 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ AnythingLLM divides your documents into objects called `workspaces`. A Workspace - [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) - [AWS Bedrock](https://aws.amazon.com/bedrock/) - [Anthropic](https://www.anthropic.com/) +- [NVIDIA NIM (chat models)](https://build.nvidia.com/explore/discover) - [Google Gemini Pro](https://ai.google.dev/) - [Hugging Face (chat models)](https://huggingface.co/) - [Ollama (chat models)](https://ollama.ai/) diff --git a/docker/.env.example b/docker/.env.example index 2b3d10629d..ee53c718bc 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -117,6 +117,10 @@ GID='1000' # XAI_LLM_API_KEY='xai-your-api-key-here' # XAI_LLM_MODEL_PREF='grok-beta' +# LLM_PROVIDER='nvidia-nim' +# NVIDIA_NIM_LLM_BASE_PATH='http://127.0.0.1:8000' +# NVIDIA_NIM_LLM_MODEL_PREF='meta/llama-3.2-3b-instruct' + ########################################### ######## Embedding API SElECTION ########## ########################################### diff --git a/frontend/src/components/LLMSelection/NvidiaNimOptions/index.jsx b/frontend/src/components/LLMSelection/NvidiaNimOptions/index.jsx new file mode 100644 index 0000000000..ed8747a8fe --- /dev/null +++ b/frontend/src/components/LLMSelection/NvidiaNimOptions/index.jsx @@ -0,0 +1,11 @@ +import RemoteNvidiaNimOptions from "./remote"; +import ManagedNvidiaNimOptions from "./managed"; + +export default function NvidiaNimOptions({ settings }) { + const version = "remote"; // static to "remote" when in docker version. + return version === "remote" ? ( + + ) : ( + + ); +} diff --git a/frontend/src/components/LLMSelection/NvidiaNimOptions/managed.jsx b/frontend/src/components/LLMSelection/NvidiaNimOptions/managed.jsx new file mode 100644 index 0000000000..0dce898abc --- /dev/null +++ b/frontend/src/components/LLMSelection/NvidiaNimOptions/managed.jsx @@ -0,0 +1,7 @@ +/** + * This component is used to select, start, and manage NVIDIA NIM + * containers and images via docker management tools. + */ +export default function ManagedNvidiaNimOptions({ settings }) { + return null; +} diff --git a/frontend/src/components/LLMSelection/NvidiaNimOptions/remote.jsx b/frontend/src/components/LLMSelection/NvidiaNimOptions/remote.jsx new file mode 100644 index 0000000000..f1fa4153d9 --- /dev/null +++ b/frontend/src/components/LLMSelection/NvidiaNimOptions/remote.jsx @@ -0,0 +1,130 @@ +import PreLoader from "@/components/Preloader"; +import useProviderEndpointAutoDiscovery from "@/hooks/useProviderEndpointAutoDiscovery"; +import System from "@/models/system"; +import { NVIDIA_NIM_COMMON_URLS } from "@/utils/constants"; +import { useState, useEffect } from "react"; + +/** + * This component is used to select a remote Nvidia NIM model endpoint + * This is the default component and way to connect to NVIDIA NIM + * as the "managed" provider can only work in the Desktop context. + */ +export default function RemoteNvidiaNimOptions({ settings }) { + const { + autoDetecting: loading, + basePath, + basePathValue, + handleAutoDetectClick, + } = useProviderEndpointAutoDiscovery({ + provider: "nvidia-nim", + initialBasePath: settings?.NvidiaNimLLMBasePath, + ENDPOINTS: NVIDIA_NIM_COMMON_URLS, + }); + + return ( +
+
+
+ + {loading ? ( + + ) : ( + <> + {!basePathValue.value && ( + + )} + + )} +
+ +

+ Enter the URL where Nvidia NIM is running. +

+
+ {!settings?.credentialsOnly && ( + + )} +
+ ); +} +function NvidiaNimModelSelection({ settings, basePath }) { + const [models, setModels] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function findCustomModels() { + setLoading(true); + const { models } = await System.customModels( + "nvidia-nim", + null, + basePath + ); + setModels(models); + setLoading(false); + } + findCustomModels(); + }, [basePath]); + + if (loading || models.length === 0) { + return ( +
+ + +
+ ); + } + + return ( +
+ + +
+ ); +} diff --git a/frontend/src/hooks/useGetProvidersModels.js b/frontend/src/hooks/useGetProvidersModels.js index 57cf650188..52e84122ba 100644 --- a/frontend/src/hooks/useGetProvidersModels.js +++ b/frontend/src/hooks/useGetProvidersModels.js @@ -43,6 +43,7 @@ const PROVIDER_DEFAULT_MODELS = { ollama: [], togetherai: [], fireworksai: [], + "nvidia-nim": [], groq: [], native: [], cohere: [ diff --git a/frontend/src/media/llmprovider/nvidia-nim.png b/frontend/src/media/llmprovider/nvidia-nim.png new file mode 100644 index 0000000000000000000000000000000000000000..cdec289cf6f28b4a6cb62c868525ed2f9a7d740f GIT binary patch literal 65902 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 literal 0 HcmV?d00001 diff --git a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx index 14c5ea11d0..f4cead2b81 100644 --- a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx +++ b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx @@ -29,6 +29,7 @@ import AWSBedrockLogo from "@/media/llmprovider/bedrock.png"; import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import APIPieLogo from "@/media/llmprovider/apipie.png"; import XAILogo from "@/media/llmprovider/xai.png"; +import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png"; import PreLoader from "@/components/Preloader"; import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions"; @@ -56,6 +57,7 @@ import AWSBedrockLLMOptions from "@/components/LLMSelection/AwsBedrockLLMOptions import DeepSeekOptions from "@/components/LLMSelection/DeepSeekOptions"; import ApiPieLLMOptions from "@/components/LLMSelection/ApiPieOptions"; import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions"; +import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions"; import LLMItem from "@/components/LLMSelection/LLMItem"; import { CaretUpDown, MagnifyingGlass, X } from "@phosphor-icons/react"; @@ -94,6 +96,15 @@ export const AVAILABLE_LLM_PROVIDERS = [ description: "Google's largest and most capable AI model", requiredConfig: ["GeminiLLMApiKey"], }, + { + name: "Nvidia NIM", + value: "nvidia-nim", + logo: NvidiaNimLogo, + options: (settings) => , + description: + "Run full parameter LLMs directly on your GPU using Nvidia's inference microservice via Docker.", + requiredConfig: ["NvidiaNimLLMBasePath"], + }, { name: "HuggingFace", value: "huggingface", diff --git a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx index ab83a5af24..1fefca235b 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx @@ -9,6 +9,7 @@ import GeminiLogo from "@/media/llmprovider/gemini.png"; import OllamaLogo from "@/media/llmprovider/ollama.png"; import TogetherAILogo from "@/media/llmprovider/togetherai.png"; import FireworksAILogo from "@/media/llmprovider/fireworksai.jpeg"; +import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png"; import LMStudioLogo from "@/media/llmprovider/lmstudio.png"; import LocalAiLogo from "@/media/llmprovider/localai.png"; import MistralLogo from "@/media/llmprovider/mistral.jpeg"; @@ -76,6 +77,13 @@ export const LLM_SELECTION_PRIVACY = { ], logo: GeminiLogo, }, + "nvidia-nim": { + name: "Nvidia NIM", + description: [ + "Your model and chats are only accessible on the machine running the Nvidia NIM service", + ], + logo: NvidiaNimLogo, + }, lmstudio: { name: "LMStudio", description: [ diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx index 69704f19c7..ea78d6c05c 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx @@ -24,7 +24,7 @@ import DeepSeekLogo from "@/media/llmprovider/deepseek.png"; import APIPieLogo from "@/media/llmprovider/apipie.png"; import NovitaLogo from "@/media/llmprovider/novita.png"; import XAILogo from "@/media/llmprovider/xai.png"; - +import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png"; import CohereLogo from "@/media/llmprovider/cohere.png"; import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions"; import GenericOpenAiOptions from "@/components/LLMSelection/GenericOpenAiOptions"; @@ -51,6 +51,7 @@ import DeepSeekOptions from "@/components/LLMSelection/DeepSeekOptions"; import ApiPieLLMOptions from "@/components/LLMSelection/ApiPieOptions"; import NovitaLLMOptions from "@/components/LLMSelection/NovitaLLMOptions"; import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions"; +import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions"; import LLMItem from "@/components/LLMSelection/LLMItem"; import System from "@/models/system"; @@ -91,6 +92,14 @@ const LLMS = [ options: (settings) => , description: "Google's largest and most capable AI model", }, + { + name: "Nvidia NIM", + value: "nvidia-nim", + logo: NvidiaNimLogo, + options: (settings) => , + description: + "Run full parameter LLMs directly on your GPU using Nvidia's inference microservice via Docker.", + }, { name: "HuggingFace", value: "huggingface", diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx index 6025d29537..effc7b7448 100644 --- a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx @@ -28,6 +28,7 @@ const ENABLED_PROVIDERS = [ "litellm", "apipie", "xai", + "nvidia-nim", // TODO: More agent support. // "cohere", // Has tool calling and will need to build explicit support // "huggingface" // Can be done but already has issues with no-chat templated. Needs to be tested. diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 6333ef8bf9..71f3048b72 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -37,6 +37,13 @@ export const LOCALAI_COMMON_URLS = [ "http://172.17.0.1:8080/v1", ]; +export const NVIDIA_NIM_COMMON_URLS = [ + "http://127.0.0.1:8000/v1/version", + "http://localhost:8000/v1/version", + "http://host.docker.internal:8000/v1/version", + "http://172.17.0.1:8000/v1/version", +]; + export function fullApiUrl() { if (API_BASE !== "/api") return API_BASE; return `${window.location.origin}/api`; diff --git a/server/.env.example b/server/.env.example index ba56517a8f..fb84a9f8d2 100644 --- a/server/.env.example +++ b/server/.env.example @@ -107,6 +107,10 @@ SIG_SALT='salt' # Please generate random string at least 32 chars long. # XAI_LLM_API_KEY='xai-your-api-key-here' # XAI_LLM_MODEL_PREF='grok-beta' +# LLM_PROVIDER='nvidia-nim' +# NVIDIA_NIM_LLM_BASE_PATH='http://127.0.0.1:8000' +# NVIDIA_NIM_LLM_MODEL_PREF='meta/llama-3.2-3b-instruct' + ########################################### ######## Embedding API SElECTION ########## ########################################### diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 58011d868a..3403c0824c 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -547,6 +547,11 @@ const SystemSettings = { // xAI LLM API Keys XAIApiKey: !!process.env.XAI_LLM_API_KEY, XAIModelPref: process.env.XAI_LLM_MODEL_PREF, + + // Nvidia NIM Keys + NvidiaNimLLMBasePath: process.env.NVIDIA_NIM_LLM_BASE_PATH, + NvidiaNimLLMModelPref: process.env.NVIDIA_NIM_LLM_MODEL_PREF, + NvidiaNimLLMTokenLimit: process.env.NVIDIA_NIM_LLM_MODEL_TOKEN_LIMIT, }; }, diff --git a/server/utils/AiProviders/nvidiaNim/index.js b/server/utils/AiProviders/nvidiaNim/index.js new file mode 100644 index 0000000000..6deb7b2e4a --- /dev/null +++ b/server/utils/AiProviders/nvidiaNim/index.js @@ -0,0 +1,220 @@ +const { NativeEmbedder } = require("../../EmbeddingEngines/native"); +const { + handleDefaultStreamResponseV2, +} = require("../../helpers/chat/responses"); + +class NvidiaNimLLM { + constructor(embedder = null, modelPreference = null) { + if (!process.env.NVIDIA_NIM_LLM_BASE_PATH) + throw new Error("No Nvidia NIM API Base Path was set."); + + const { OpenAI: OpenAIApi } = require("openai"); + this.nvidiaNim = new OpenAIApi({ + baseURL: parseNvidiaNimBasePath(process.env.NVIDIA_NIM_LLM_BASE_PATH), + apiKey: null, + }); + + this.model = modelPreference || process.env.NVIDIA_NIM_LLM_MODEL_PREF; + this.limits = { + history: this.promptWindowLimit() * 0.15, + system: this.promptWindowLimit() * 0.15, + user: this.promptWindowLimit() * 0.7, + }; + + this.embedder = embedder ?? new NativeEmbedder(); + this.defaultTemp = 0.7; + this.#log( + `Loaded with model: ${this.model} with context window: ${this.promptWindowLimit()}` + ); + } + + #log(text, ...args) { + console.log(`\x1b[36m[${this.constructor.name}]\x1b[0m ${text}`, ...args); + } + + #appendContext(contextTexts = []) { + if (!contextTexts || !contextTexts.length) return ""; + return ( + "\nContext:\n" + + contextTexts + .map((text, i) => { + return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`; + }) + .join("") + ); + } + + /** + * Set the model token limit `NVIDIA_NIM_LLM_TOKEN_LIMIT` for the given model ID + * @param {string} modelId + * @param {string} basePath + * @returns {Promise} + */ + static async setModelTokenLimit(modelId, basePath = null) { + if (!modelId) return; + const { OpenAI: OpenAIApi } = require("openai"); + const openai = new OpenAIApi({ + baseURL: parseNvidiaNimBasePath( + basePath || process.env.NVIDIA_NIM_LLM_BASE_PATH + ), + apiKey: null, + }); + const model = await openai.models + .list() + .then((results) => results.data) + .catch(() => { + return []; + }); + + if (!model.length) return; + const modelInfo = model.find((model) => model.id === modelId); + if (!modelInfo) return; + process.env.NVIDIA_NIM_LLM_TOKEN_LIMIT = Number( + modelInfo.max_model_len || 4096 + ); + } + + streamingEnabled() { + return "streamGetChatCompletion" in this; + } + + static promptWindowLimit(_modelName) { + const limit = process.env.NVIDIA_NIM_LLM_MODEL_TOKEN_LIMIT || 4096; + if (!limit || isNaN(Number(limit))) + throw new Error("No Nvidia NIM token context limit was set."); + return Number(limit); + } + + // Ensure the user set a value for the token limit + // and if undefined - assume 4096 window. + promptWindowLimit() { + const limit = process.env.NVIDIA_NIM_LLM_MODEL_TOKEN_LIMIT || 4096; + if (!limit || isNaN(Number(limit))) + throw new Error("No Nvidia NIM token context limit was set."); + return Number(limit); + } + + async isValidChatCompletionModel(_ = "") { + return true; + } + + /** + * Generates appropriate content array for a message + attachments. + * @param {{userPrompt:string, attachments: import("../../helpers").Attachment[]}} + * @returns {string|object[]} + */ + #generateContent({ userPrompt, attachments = [] }) { + if (!attachments.length) { + return userPrompt; + } + + const content = [{ type: "text", text: userPrompt }]; + for (let attachment of attachments) { + content.push({ + type: "image_url", + image_url: { + url: attachment.contentString, + detail: "auto", + }, + }); + } + return content.flat(); + } + + /** + * Construct the user prompt for this model. + * @param {{attachments: import("../../helpers").Attachment[]}} param0 + * @returns + */ + constructPrompt({ + systemPrompt = "", + contextTexts = [], + chatHistory = [], + userPrompt = "", + attachments = [], + }) { + const prompt = { + role: "system", + content: `${systemPrompt}${this.#appendContext(contextTexts)}`, + }; + return [ + prompt, + ...chatHistory, + { + role: "user", + content: this.#generateContent({ userPrompt, attachments }), + }, + ]; + } + + async getChatCompletion(messages = null, { temperature = 0.7 }) { + if (!this.model) + throw new Error( + `Nvidia NIM chat: ${this.model} is not valid or defined model for chat completion!` + ); + + const result = await this.nvidiaNim.chat.completions.create({ + model: this.model, + messages, + temperature, + }); + + if (!result.hasOwnProperty("choices") || result.choices.length === 0) + return null; + return result.choices[0].message.content; + } + + async streamGetChatCompletion(messages = null, { temperature = 0.7 }) { + if (!this.model) + throw new Error( + `Nvidia NIM chat: ${this.model} is not valid or defined model for chat completion!` + ); + + const streamRequest = await this.nvidiaNim.chat.completions.create({ + model: this.model, + stream: true, + messages, + temperature, + }); + return streamRequest; + } + + handleStream(response, stream, responseProps) { + return handleDefaultStreamResponseV2(response, stream, responseProps); + } + + // 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); + return await messageArrayCompressor(this, messageArray, rawHistory); + } +} + +/** + * Parse the base path for the Nvidia NIM container API. Since the base path must end in /v1 and cannot have a trailing slash, + * and the user can possibly set it to anything and likely incorrectly due to pasting behaviors, we need to ensure it is in the correct format. + * @param {string} basePath + * @returns {string} + */ +function parseNvidiaNimBasePath(providedBasePath = "") { + try { + const baseURL = new URL(providedBasePath); + const basePath = `${baseURL.origin}/v1`; + return basePath; + } catch (e) { + return providedBasePath; + } +} + +module.exports = { + NvidiaNimLLM, + parseNvidiaNimBasePath, +}; diff --git a/server/utils/agents/aibitat/index.js b/server/utils/agents/aibitat/index.js index d61867f4d4..3c2faf5b16 100644 --- a/server/utils/agents/aibitat/index.js +++ b/server/utils/agents/aibitat/index.js @@ -783,6 +783,8 @@ ${this.getHistory({ to: route.to }) return new Providers.AWSBedrockProvider({}); case "fireworksai": return new Providers.FireworksAIProvider({ model: config.model }); + case "nvidia-nim": + return new Providers.NvidiaNimProvider({ model: config.model }); case "deepseek": return new Providers.DeepSeekProvider({ model: config.model }); case "litellm": diff --git a/server/utils/agents/aibitat/providers/ai-provider.js b/server/utils/agents/aibitat/providers/ai-provider.js index 4ba6840d70..bd7920611b 100644 --- a/server/utils/agents/aibitat/providers/ai-provider.js +++ b/server/utils/agents/aibitat/providers/ai-provider.js @@ -155,6 +155,14 @@ class Provider { apiKey: process.env.XAI_LLM_API_KEY ?? null, ...config, }); + case "novita": + return new ChatOpenAI({ + configuration: { + baseURL: "https://api.novita.ai/v3/openai", + }, + apiKey: process.env.NOVITA_LLM_API_KEY ?? null, + ...config, + }); // OSS Model Runners // case "anythingllm_ollama": @@ -207,12 +215,12 @@ class Provider { apiKey: process.env.LITE_LLM_API_KEY ?? null, ...config, }); - case "novita": + case "nvidia-nim": return new ChatOpenAI({ configuration: { - baseURL: "https://api.novita.ai/v3/openai", + baseURL: process.env.NVIDIA_NIM_LLM_BASE_PATH, }, - apiKey: process.env.NOVITA_LLM_API_KEY ?? null, + apiKey: null, ...config, }); diff --git a/server/utils/agents/aibitat/providers/index.js b/server/utils/agents/aibitat/providers/index.js index c454c39387..e5c01123c6 100644 --- a/server/utils/agents/aibitat/providers/index.js +++ b/server/utils/agents/aibitat/providers/index.js @@ -19,6 +19,7 @@ const LiteLLMProvider = require("./litellm.js"); const ApiPieProvider = require("./apipie.js"); const XAIProvider = require("./xai.js"); const NovitaProvider = require("./novita.js"); +const NvidiaNimProvider = require("./nvidiaNim.js"); module.exports = { OpenAIProvider, @@ -42,4 +43,5 @@ module.exports = { ApiPieProvider, XAIProvider, NovitaProvider, + NvidiaNimProvider, }; diff --git a/server/utils/agents/aibitat/providers/nvidiaNim.js b/server/utils/agents/aibitat/providers/nvidiaNim.js new file mode 100644 index 0000000000..50132abcb5 --- /dev/null +++ b/server/utils/agents/aibitat/providers/nvidiaNim.js @@ -0,0 +1,117 @@ +const OpenAI = require("openai"); +const Provider = require("./ai-provider.js"); +const InheritMultiple = require("./helpers/classes.js"); +const UnTooled = require("./helpers/untooled.js"); + +/** + * The agent provider for the Nvidia NIM provider. + * We wrap Nvidia NIM in UnTooled because its tool-calling may not be supported for specific models and this normalizes that. + */ +class NvidiaNimProvider extends InheritMultiple([Provider, UnTooled]) { + model; + + constructor(config = {}) { + const { model } = config; + super(); + const client = new OpenAI({ + baseURL: process.env.NVIDIA_NIM_LLM_BASE_PATH, + apiKey: null, + maxRetries: 0, + }); + + this._client = client; + this.model = model; + this.verbose = true; + } + + get client() { + return this._client; + } + + async #handleFunctionCallChat({ messages = [] }) { + return await this.client.chat.completions + .create({ + model: this.model, + temperature: 0, + messages, + }) + .then((result) => { + if (!result.hasOwnProperty("choices")) + throw new Error("Nvidia NIM chat: No results!"); + if (result.choices.length === 0) + throw new Error("Nvidia NIM chat: No results length!"); + return result.choices[0].message.content; + }) + .catch((_) => { + return null; + }); + } + + /** + * Create a completion based on the received messages. + * + * @param messages A list of messages to send to the API. + * @param functions + * @returns The completion. + */ + async complete(messages, functions = null) { + try { + let completion; + if (functions.length > 0) { + const { toolCall, text } = await this.functionCall( + messages, + functions, + this.#handleFunctionCallChat.bind(this) + ); + + if (toolCall !== null) { + this.providerLog(`Valid tool call found - running ${toolCall.name}.`); + this.deduplicator.trackRun(toolCall.name, toolCall.arguments); + return { + result: null, + functionCall: { + name: toolCall.name, + arguments: toolCall.arguments, + }, + cost: 0, + }; + } + completion = { content: text }; + } + + if (!completion?.content) { + this.providerLog( + "Will assume chat completion without tool call inputs." + ); + const response = await this.client.chat.completions.create({ + model: this.model, + messages: this.cleanMsgs(messages), + }); + completion = response.choices[0].message; + } + + // The UnTooled class inherited Deduplicator is mostly useful to prevent the agent + // from calling the exact same function over and over in a loop within a single chat exchange + // _but_ we should enable it to call previously used tools in a new chat interaction. + this.deduplicator.reset("runs"); + return { + result: completion.content, + cost: 0, + }; + } catch (error) { + throw error; + } + } + + /** + * Get the cost of the completion. + * + * @param _usage The completion to get the cost for. + * @returns The cost of the completion. + */ + getCost(_usage) { + return 0; + } +} + +module.exports = NvidiaNimProvider; diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js index 6b1d42af29..2263a59682 100644 --- a/server/utils/agents/index.js +++ b/server/utils/agents/index.js @@ -177,6 +177,12 @@ class AgentHandler { if (!process.env.NOVITA_LLM_API_KEY) throw new Error("Novita API Key must be provided to use agents."); break; + case "nvidia-nim": + if (!process.env.NVIDIA_NIM_LLM_BASE_PATH) + throw new Error( + "Nvidia NIM base path must be provided to use agents." + ); + break; default: throw new Error( @@ -240,6 +246,8 @@ class AgentHandler { return process.env.XAI_LLM_MODEL_PREF ?? "grok-beta"; case "novita": return process.env.NOVITA_LLM_MODEL_PREF ?? "gryphe/mythomax-l2-13b"; + case "nvidia-nim": + return process.env.NVIDIA_NIM_LLM_MODEL_PREF ?? null; default: return null; } diff --git a/server/utils/helpers/customModels.js b/server/utils/helpers/customModels.js index 72882d6d1c..35ab5570d0 100644 --- a/server/utils/helpers/customModels.js +++ b/server/utils/helpers/customModels.js @@ -6,6 +6,8 @@ const { fireworksAiModels } = require("../AiProviders/fireworksAi"); const { ElevenLabsTTS } = require("../TextToSpeech/elevenLabs"); const { fetchNovitaModels } = require("../AiProviders/novita"); const { parseLMStudioBasePath } = require("../AiProviders/lmStudio"); +const { parseNvidiaNimBasePath } = require("../AiProviders/nvidiaNim"); + const SUPPORT_CUSTOM_MODELS = [ "openai", "localai", @@ -13,6 +15,7 @@ const SUPPORT_CUSTOM_MODELS = [ "native-llm", "togetherai", "fireworksai", + "nvidia-nim", "mistral", "perplexity", "openrouter", @@ -68,6 +71,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) { return await getNovitaModels(); case "xai": return await getXAIModels(apiKey); + case "nvidia-nim": + return await getNvidiaNimModels(basePath); default: return { models: [], error: "Invalid provider for custom models" }; } @@ -520,6 +525,37 @@ async function getXAIModels(_apiKey = null) { return { models, error: null }; } +async function getNvidiaNimModels(basePath = null) { + try { + const { OpenAI: OpenAIApi } = require("openai"); + const openai = new OpenAIApi({ + baseURL: parseNvidiaNimBasePath( + basePath ?? process.env.NVIDIA_NIM_LLM_BASE_PATH + ), + apiKey: null, + }); + const modelResponse = await openai.models + .list() + .then((results) => results.data) + .catch((e) => { + throw new Error(e.message); + }); + + const models = modelResponse.map((model) => { + return { + id: model.id, + name: model.id, + organization: model.owned_by, + }; + }); + + return { models, error: null }; + } catch (e) { + console.error(`Nvidia NIM:getNvidiaNimModels`, e.message); + return { models: [], error: "Could not fetch Nvidia NIM Models" }; + } +} + module.exports = { getCustomModels, }; diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index cbf07fbd0e..e599078b6a 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -171,6 +171,9 @@ function getLLMProvider({ provider = null, model = null } = {}) { case "xai": const { XAiLLM } = require("../AiProviders/xai"); return new XAiLLM(embedder, model); + case "nvidia-nim": + const { NvidiaNimLLM } = require("../AiProviders/nvidiaNim"); + return new NvidiaNimLLM(embedder, model); default: throw new Error( `ENV: No valid LLM_PROVIDER value found in environment! Using ${process.env.LLM_PROVIDER}` @@ -309,6 +312,9 @@ function getLLMProviderClass({ provider = null } = {}) { case "xai": const { XAiLLM } = require("../AiProviders/xai"); return XAiLLM; + case "nvidia-nim": + const { NvidiaNimLLM } = require("../AiProviders/nvidiaNim"); + return NvidiaNimLLM; default: return null; } diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 0af4b839b3..3165dc40a3 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -578,6 +578,29 @@ const KEY_MAPPING = { envKey: "XAI_LLM_MODEL_PREF", checks: [isNotEmpty], }, + + // Nvidia NIM Options + NvidiaNimLLMBasePath: { + envKey: "NVIDIA_NIM_LLM_BASE_PATH", + checks: [isValidURL], + postUpdate: [ + (_, __, nextValue) => { + const { parseNvidiaNimBasePath } = require("../AiProviders/nvidiaNim"); + process.env.NVIDIA_NIM_LLM_BASE_PATH = + parseNvidiaNimBasePath(nextValue); + }, + ], + }, + NvidiaNimLLMModelPref: { + envKey: "NVIDIA_NIM_LLM_MODEL_PREF", + checks: [], + postUpdate: [ + async (_, __, nextValue) => { + const { NvidiaNimLLM } = require("../AiProviders/nvidiaNim"); + await NvidiaNimLLM.setModelTokenLimit(nextValue); + }, + ], + }, }; function isNotEmpty(input = "") { @@ -684,6 +707,7 @@ function supportedLLM(input = "") { "deepseek", "apipie", "xai", + "nvidia-nim", ].includes(input); return validSelection ? null : `${input} is not a valid LLM provider.`; }