From e7df34ea740c9c5f8c33a636856c0111a7c5f230 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 15 May 2026 13:30:26 +0100 Subject: [PATCH] feat(core): Add NVIDIA Nemotron Models with cloud and self-hosted NIM support (#29618) --- packages/@n8n/api-types/src/chat-hub.ts | 13 +- .../credentials/NvidiaApi.credentials.ts | 60 ++++ .../llms/LmChatNvidia/LmChatNvidia.node.ts | 273 ++++++++++++++++++ .../nodes/llms/LmChatNvidia/nvidia.dark.svg | 1 + .../nodes/llms/LmChatNvidia/nvidia.svg | 1 + .../LmChatNvidia/test/LmChatNvidia.test.ts | 199 +++++++++++++ packages/@n8n/nodes-langchain/package.json | 2 + ...hat-hub-models.service.integration.test.ts | 1 + .../chat-hub/chat-hub-workflow.service.ts | 9 + .../modules/chat-hub/chat-hub.constants.ts | 4 + .../chat-hub/chat-hub.models.service.ts | 53 ++++ .../src/modules/chat-hub/context-limits.ts | 1 + .../src/features/ai/chatHub/__test__/data.ts | 8 + .../src/features/ai/chatHub/constants.ts | 1 + 14 files changed, 625 insertions(+), 1 deletion(-) create mode 100644 packages/@n8n/nodes-langchain/credentials/NvidiaApi.credentials.ts create mode 100644 packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/LmChatNvidia.node.ts create mode 100644 packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/nvidia.dark.svg create mode 100644 packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/nvidia.svg create mode 100644 packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/test/LmChatNvidia.test.ts diff --git a/packages/@n8n/api-types/src/chat-hub.ts b/packages/@n8n/api-types/src/chat-hub.ts index 33e371b025e..8ea1fb84f31 100644 --- a/packages/@n8n/api-types/src/chat-hub.ts +++ b/packages/@n8n/api-types/src/chat-hub.ts @@ -32,6 +32,7 @@ export const chatHubLLMProviderSchema = z.enum([ 'deepSeek', 'cohere', 'mistralCloud', + 'nvidia', ]); export type ChatHubLLMProvider = z.infer; @@ -97,6 +98,7 @@ export const PROVIDER_CREDENTIAL_TYPE_MAP: Record = deepSeek: 'deepSeekApi', cohere: 'cohereApi', mistralCloud: 'mistralCloudApi', + nvidia: 'nvidiaApi', }; export const VECTOR_STORE_PROVIDER_CREDENTIAL_TYPE_MAP: Record = @@ -179,6 +181,11 @@ const mistralCloudModelSchema = z.object({ model: z.string(), }); +const nvidiaModelSchema = z.object({ + provider: z.literal('nvidia'), + model: z.string(), +}); + const n8nModelSchema = z.object({ provider: z.literal('n8n'), workflowId: z.string(), @@ -204,6 +211,7 @@ export const chatHubConversationModelSchema = z.discriminatedUnion('provider', [ deepSeekModelSchema, cohereModelSchema, mistralCloudModelSchema, + nvidiaModelSchema, n8nModelSchema, chatAgentSchema, ]); @@ -222,6 +230,7 @@ export type ChatHubOpenRouterModel = z.infer; export type ChatHubDeepSeekModel = z.infer; export type ChatHubCohereModel = z.infer; export type ChatHubMistralCloudModel = z.infer; +export type ChatHubNvidiaModel = z.infer; export type ChatHubBaseLLMModel = | ChatHubOpenAIModel | ChatHubAnthropicModel @@ -236,7 +245,8 @@ export type ChatHubBaseLLMModel = | ChatHubOpenRouterModel | ChatHubDeepSeekModel | ChatHubCohereModel - | ChatHubMistralCloudModel; + | ChatHubMistralCloudModel + | ChatHubNvidiaModel; export type ChatHubN8nModel = z.infer; export type ChatHubCustomAgentModel = z.infer; @@ -302,6 +312,7 @@ export const emptyChatModelsResponse: ChatModelsResponse = { deepSeek: { models: [] }, cohere: { models: [] }, mistralCloud: { models: [] }, + nvidia: { models: [] }, n8n: { models: [] }, // eslint-disable-next-line @typescript-eslint/naming-convention 'custom-agent': { models: [] }, diff --git a/packages/@n8n/nodes-langchain/credentials/NvidiaApi.credentials.ts b/packages/@n8n/nodes-langchain/credentials/NvidiaApi.credentials.ts new file mode 100644 index 00000000000..a527b2e5860 --- /dev/null +++ b/packages/@n8n/nodes-langchain/credentials/NvidiaApi.credentials.ts @@ -0,0 +1,60 @@ +import type { + ICredentialDataDecryptedObject, + ICredentialTestRequest, + ICredentialType, + IHttpRequestOptions, + INodeProperties, +} from 'n8n-workflow'; + +export class NvidiaApi implements ICredentialType { + name = 'nvidiaApi'; + + displayName = 'NVIDIA Nemotron'; + + documentationUrl = 'nvidia'; + + properties: INodeProperties[] = [ + { + displayName: 'Base URL', + name: 'url', + type: 'string', + required: true, + default: 'https://integrate.api.nvidia.com/v1', + description: + 'Use the default for build.nvidia.com cloud, or change it to point at a self-hosted NIM container (e.g. http://localhost:8000/v1)', + }, + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + typeOptions: { password: true }, + required: false, + default: '', + description: + 'Required for build.nvidia.com cloud. Leave blank for a self-hosted NIM that does not require authentication', + }, + ]; + + authenticate = async ( + credentials: ICredentialDataDecryptedObject, + requestOptions: IHttpRequestOptions, + ): Promise => { + if (!credentials.apiKey) { + return requestOptions; + } + return { + ...requestOptions, + headers: { + ...requestOptions.headers, + Authorization: `Bearer ${credentials.apiKey as string}`, + }, + }; + }; + + test: ICredentialTestRequest = { + request: { + baseURL: '={{ $credentials.url }}', + url: '/models', + }, + }; +} diff --git a/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/LmChatNvidia.node.ts b/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/LmChatNvidia.node.ts new file mode 100644 index 00000000000..c4c8d674b25 --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/LmChatNvidia.node.ts @@ -0,0 +1,273 @@ +import { ChatOpenAI, type ClientOptions } from '@langchain/openai'; +import { + getProxyAgent, + makeN8nLlmFailedAttemptHandler, + N8nLlmTracing, + getConnectionHintNoticeField, +} from '@n8n/ai-utilities'; +import { + NodeConnectionTypes, + type INodeType, + type INodeTypeDescription, + type ISupplyDataFunctions, + type SupplyData, +} from 'n8n-workflow'; + +import type { OpenAICompatibleCredential } from '../../../types/types'; +import { openAiFailedAttemptHandler } from '../../vendors/OpenAi/helpers/error-handling'; + +const NEMOTRON_FALLBACK_MODELS = [ + 'nvidia/llama-3.3-nemotron-super-49b-v1', + 'nvidia/llama-3.1-nemotron-70b-instruct', + 'nvidia/llama-3.1-nemotron-nano-8b-v1', + 'nvidia/nemotron-4-340b-instruct', + 'nvidia/nemotron-mini-4b-instruct', +]; + +export class LmChatNvidia implements INodeType { + description: INodeTypeDescription = { + displayName: 'NVIDIA Nemotron Chat Model', + + name: 'lmChatNvidia', + icon: { light: 'file:nvidia.svg', dark: 'file:nvidia.dark.svg' }, + group: ['transform'], + version: [1], + description: 'NVIDIA Nemotron models from build.nvidia.com or self-hosted NIM', + defaults: { + name: 'NVIDIA Nemotron Chat Model', + }, + codex: { + categories: ['AI'], + subcategories: { + AI: ['Language Models', 'Root Nodes'], + 'Language Models': ['Chat Models (Recommended)'], + }, + resources: { + primaryDocumentation: [ + { + url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.lmchatnvidia/', + }, + ], + }, + }, + + inputs: [], + + outputs: [NodeConnectionTypes.AiLanguageModel], + outputNames: ['Model'], + credentials: [ + { + name: 'nvidiaApi', + required: true, + }, + ], + requestDefaults: { + ignoreHttpStatusErrors: true, + baseURL: '={{ $credentials?.url }}', + }, + properties: [ + getConnectionHintNoticeField([NodeConnectionTypes.AiChain, NodeConnectionTypes.AiAgent]), + { + displayName: + 'If using JSON response format, you must include word "json" in the prompt in your chain or agent.', + name: 'notice', + type: 'notice', + default: '', + displayOptions: { + show: { + '/options.responseFormat': ['json_object'], + }, + }, + }, + { + displayName: 'Model', + name: 'model', + type: 'options', + description: + 'The Nemotron model which will generate the completion. Learn more.', + typeOptions: { + loadOptions: { + routing: { + request: { + method: 'GET', + url: '/models', + }, + output: { + postReceive: [ + { + type: 'rootProperty', + properties: { + property: 'data', + }, + }, + { + type: 'filter', + properties: { + pass: '={{ /nemotron/i.test($responseItem.id) }}', + }, + }, + { + type: 'setKeyValue', + properties: { + name: '={{$responseItem.id}}', + value: '={{$responseItem.id}}', + }, + }, + { + type: 'sort', + properties: { + key: 'name', + }, + }, + ], + }, + }, + }, + }, + routing: { + send: { + type: 'body', + property: 'model', + }, + }, + default: 'nvidia/llama-3.3-nemotron-super-49b-v1', + options: NEMOTRON_FALLBACK_MODELS.map((id) => ({ name: id, value: id })), + }, + { + displayName: 'Options', + name: 'options', + placeholder: 'Add Option', + description: 'Additional options to add', + type: 'collection', + default: {}, + options: [ + { + displayName: 'Frequency Penalty', + name: 'frequencyPenalty', + default: 0, + typeOptions: { maxValue: 2, minValue: -2, numberPrecision: 1 }, + description: + "Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim", + type: 'number', + }, + { + displayName: 'Maximum Number of Tokens', + name: 'maxTokens', + default: -1, + description: + 'The maximum number of tokens to generate in the completion. Use -1 for the model default.', + type: 'number', + }, + { + displayName: 'Response Format', + name: 'responseFormat', + default: 'text', + type: 'options', + options: [ + { + name: 'Text', + value: 'text', + description: 'Regular text response', + }, + { + name: 'JSON', + value: 'json_object', + description: + 'Enables JSON mode, which should guarantee the message the model generates is valid JSON', + }, + ], + }, + { + displayName: 'Presence Penalty', + name: 'presencePenalty', + default: 0, + typeOptions: { maxValue: 2, minValue: -2, numberPrecision: 1 }, + description: + "Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics", + type: 'number', + }, + { + displayName: 'Sampling Temperature', + name: 'temperature', + default: 0.7, + typeOptions: { maxValue: 2, minValue: 0, numberPrecision: 1 }, + description: + 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.', + type: 'number', + }, + { + displayName: 'Timeout', + name: 'timeout', + default: 360000, + description: 'Maximum amount of time a request is allowed to take in milliseconds', + type: 'number', + }, + { + displayName: 'Max Retries', + name: 'maxRetries', + default: 2, + description: 'Maximum number of retries to attempt', + type: 'number', + }, + { + displayName: 'Top P', + name: 'topP', + default: 1, + typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 }, + description: + 'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. We generally recommend altering this or temperature but not both.', + type: 'number', + }, + ], + }, + ], + }; + + async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise { + const credentials = await this.getCredentials('nvidiaApi'); + + const modelName = this.getNodeParameter('model', itemIndex) as string; + + const options = this.getNodeParameter('options', itemIndex, {}) as { + frequencyPenalty?: number; + maxTokens?: number; + maxRetries: number; + timeout: number; + presencePenalty?: number; + temperature?: number; + topP?: number; + responseFormat?: 'text' | 'json_object'; + }; + + const timeout = options.timeout; + const configuration: ClientOptions = { + baseURL: credentials.url, + fetchOptions: { + dispatcher: getProxyAgent(credentials.url, { + headersTimeout: timeout, + bodyTimeout: timeout, + }), + }, + }; + + const model = new ChatOpenAI({ + apiKey: credentials.apiKey || 'unused', + model: modelName, + ...options, + timeout, + maxRetries: options.maxRetries ?? 2, + configuration, + callbacks: [new N8nLlmTracing(this)], + modelKwargs: options.responseFormat + ? { + response_format: { type: options.responseFormat }, + } + : undefined, + onFailedAttempt: makeN8nLlmFailedAttemptHandler(this, openAiFailedAttemptHandler), + }); + + return { + response: model, + }; + } +} diff --git a/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/nvidia.dark.svg b/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/nvidia.dark.svg new file mode 100644 index 00000000000..a2873c6d93d --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/nvidia.dark.svg @@ -0,0 +1 @@ +NVIDIA diff --git a/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/nvidia.svg b/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/nvidia.svg new file mode 100644 index 00000000000..a2873c6d93d --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/nvidia.svg @@ -0,0 +1 @@ +NVIDIA diff --git a/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/test/LmChatNvidia.test.ts b/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/test/LmChatNvidia.test.ts new file mode 100644 index 00000000000..0705d67588a --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/llms/LmChatNvidia/test/LmChatNvidia.test.ts @@ -0,0 +1,199 @@ +/* eslint-disable n8n-nodes-base/node-filename-against-convention */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/unbound-method */ +import { ChatOpenAI } from '@langchain/openai'; +import { makeN8nLlmFailedAttemptHandler, getProxyAgent } from '@n8n/ai-utilities'; +import { createMockExecuteFunction } from 'n8n-nodes-base/test/nodes/Helpers'; +import type { INode, ISupplyDataFunctions } from 'n8n-workflow'; +import type { Mocked } from 'vitest'; + +import { LmChatNvidia } from '../LmChatNvidia.node'; + +vi.mock('@langchain/openai'); +vi.mock('@n8n/ai-utilities'); + +const MockedChatOpenAI = vi.mocked(ChatOpenAI); +const mockedMakeN8nLlmFailedAttemptHandler = vi.mocked(makeN8nLlmFailedAttemptHandler); +const mockedGetProxyAgent = vi.mocked(getProxyAgent); + +describe('LmChatNvidia', () => { + let node: LmChatNvidia; + + const mockNodeDef: INode = { + id: '1', + name: 'NVIDIA Nemotron Chat Model', + typeVersion: 1, + type: 'n8n-nodes-langchain.lmChatNvidia', + position: [0, 0], + parameters: {}, + }; + + const setupMockContext = ( + credentialOverrides: Partial<{ apiKey: string; url: string }> = {}, + nodeOverrides: Partial = {}, + ) => { + const nodeDef = { ...mockNodeDef, ...nodeOverrides }; + const ctx = createMockExecuteFunction( + {}, + nodeDef, + ) as Mocked; + + ctx.getCredentials = vi.fn().mockResolvedValue({ + apiKey: 'test-key', + url: 'https://integrate.api.nvidia.com/v1', + ...credentialOverrides, + }); + ctx.getNode = vi.fn().mockReturnValue(nodeDef); + ctx.getNodeParameter = vi.fn().mockImplementation((paramName: string) => { + if (paramName === 'model') return 'nvidia/llama-3.1-nemotron-70b-instruct'; + if (paramName === 'options') return {}; + return undefined; + }); + + mockedMakeN8nLlmFailedAttemptHandler.mockReturnValue(vi.fn()); + mockedGetProxyAgent.mockReturnValue({} as any); + return ctx; + }; + + beforeEach(() => { + node = new LmChatNvidia(); + vi.clearAllMocks(); + }); + + describe('node description', () => { + it('should have correct node properties', () => { + expect(node.description).toMatchObject({ + displayName: 'NVIDIA Nemotron Chat Model', + name: 'lmChatNvidia', + group: ['transform'], + version: [1], + }); + }); + + it('should require a single nvidiaApi credential', () => { + expect(node.description.credentials).toEqual([{ name: 'nvidiaApi', required: true }]); + }); + + it('should output ai_languageModel', () => { + expect(node.description.outputs).toEqual(['ai_languageModel']); + expect(node.description.outputNames).toEqual(['Model']); + }); + + it('should filter to Nemotron models in loadOptions', () => { + const modelProp = node.description.properties.find((p) => p?.name === 'model'); + expect(modelProp).toBeDefined(); + const postReceive = (modelProp?.typeOptions as any)?.loadOptions?.routing?.output + ?.postReceive as Array<{ type: string; properties: { pass?: string } }>; + const filterStep = postReceive.find((step) => step.type === 'filter'); + expect(filterStep?.properties.pass).toMatch(/nemotron/i); + }); + }); + + describe('supplyData', () => { + it('should pass credential url to ChatOpenAI configuration', async () => { + const ctx = setupMockContext(); + + const result = await node.supplyData.call(ctx, 0); + + expect(ctx.getCredentials).toHaveBeenCalledWith('nvidiaApi'); + expect(MockedChatOpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: 'test-key', + model: 'nvidia/llama-3.1-nemotron-70b-instruct', + configuration: expect.objectContaining({ + baseURL: 'https://integrate.api.nvidia.com/v1', + }), + }), + ); + expect(result).toEqual({ response: expect.any(Object) }); + }); + + it('should accept a self-hosted base URL on the same credential', async () => { + const ctx = setupMockContext({ url: 'http://localhost:8000/v1', apiKey: '' }); + + await node.supplyData.call(ctx, 0); + + expect(MockedChatOpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + configuration: expect.objectContaining({ + baseURL: 'http://localhost:8000/v1', + }), + }), + ); + }); + + it('should fall back to a placeholder apiKey when the credential has none', async () => { + const ctx = setupMockContext({ apiKey: '' }); + + await node.supplyData.call(ctx, 0); + + expect(MockedChatOpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + apiKey: 'unused', + }), + ); + }); + + it('should pass options through to ChatOpenAI', async () => { + const ctx = setupMockContext(); + ctx.getNodeParameter = vi.fn().mockImplementation((paramName: string) => { + if (paramName === 'model') return 'nvidia/llama-3.1-nemotron-70b-instruct'; + if (paramName === 'options') + return { + temperature: 0.5, + maxTokens: 2000, + topP: 0.9, + frequencyPenalty: 0.3, + presencePenalty: 0.2, + timeout: 60000, + maxRetries: 5, + }; + return undefined; + }); + + await node.supplyData.call(ctx, 0); + + expect(MockedChatOpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + temperature: 0.5, + maxTokens: 2000, + topP: 0.9, + frequencyPenalty: 0.3, + presencePenalty: 0.2, + timeout: 60000, + maxRetries: 5, + }), + ); + }); + + it('should set response_format in modelKwargs when responseFormat is provided', async () => { + const ctx = setupMockContext(); + ctx.getNodeParameter = vi.fn().mockImplementation((paramName: string) => { + if (paramName === 'model') return 'nvidia/llama-3.1-nemotron-70b-instruct'; + if (paramName === 'options') return { responseFormat: 'json_object' }; + return undefined; + }); + + await node.supplyData.call(ctx, 0); + + expect(MockedChatOpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + modelKwargs: { response_format: { type: 'json_object' } }, + }), + ); + }); + + it('should not set modelKwargs when no responseFormat', async () => { + const ctx = setupMockContext(); + + await node.supplyData.call(ctx, 0); + + expect(MockedChatOpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + modelKwargs: undefined, + }), + ); + }); + }); +}); diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json index d890c42512f..f64dfc649cd 100644 --- a/packages/@n8n/nodes-langchain/package.json +++ b/packages/@n8n/nodes-langchain/package.json @@ -66,6 +66,7 @@ "dist/credentials/MinimaxApi.credentials.js", "dist/credentials/MoonshotApi.credentials.js", "dist/credentials/LemonadeApi.credentials.js", + "dist/credentials/NvidiaApi.credentials.js", "dist/credentials/OllamaApi.credentials.js", "dist/credentials/OpenRouterApi.credentials.js", "dist/credentials/PineconeApi.credentials.js", @@ -128,6 +129,7 @@ "dist/nodes/llms/LmChatMinimax/LmChatMinimax.node.js", "dist/nodes/llms/LmChatMoonshot/LmChatMoonshot.node.js", "dist/nodes/llms/LMChatLemonade/LmChatLemonade.node.js", + "dist/nodes/llms/LmChatNvidia/LmChatNvidia.node.js", "dist/nodes/llms/LMChatOllama/LmChatOllama.node.js", "dist/nodes/llms/LmChatOpenRouter/LmChatOpenRouter.node.js", "dist/nodes/llms/LmChatVercelAiGateway/LmChatVercelAiGateway.node.js", diff --git a/packages/cli/src/modules/chat-hub/__tests__/chat-hub-models.service.integration.test.ts b/packages/cli/src/modules/chat-hub/__tests__/chat-hub-models.service.integration.test.ts index 9aca98665b0..8f40faeaa8f 100644 --- a/packages/cli/src/modules/chat-hub/__tests__/chat-hub-models.service.integration.test.ts +++ b/packages/cli/src/modules/chat-hub/__tests__/chat-hub-models.service.integration.test.ts @@ -46,6 +46,7 @@ const emptyCredentialIds = { deepSeek: null, cohere: null, mistralCloud: null, + nvidia: null, }; describe('ChatHubModelsService', () => { diff --git a/packages/cli/src/modules/chat-hub/chat-hub-workflow.service.ts b/packages/cli/src/modules/chat-hub/chat-hub-workflow.service.ts index 91b4bc64d04..a8f9240ba08 100644 --- a/packages/cli/src/modules/chat-hub/chat-hub-workflow.service.ts +++ b/packages/cli/src/modules/chat-hub/chat-hub-workflow.service.ts @@ -895,6 +895,15 @@ ${this.getSystemMessageMetadata(timeZone) + artifactContext}`; }, }; } + case 'nvidia': { + return { + ...common, + parameters: { + model, + options: {}, + }, + }; + } default: throw new OperationalError('Unsupported model provider'); } diff --git a/packages/cli/src/modules/chat-hub/chat-hub.constants.ts b/packages/cli/src/modules/chat-hub/chat-hub.constants.ts index feedddf488f..30936640e78 100644 --- a/packages/cli/src/modules/chat-hub/chat-hub.constants.ts +++ b/packages/cli/src/modules/chat-hub/chat-hub.constants.ts @@ -93,6 +93,10 @@ export const PROVIDER_NODE_TYPE_MAP: Record { + return await this.nodeParametersService.getOptionsViaLoadOptions( + { + routing: { + request: { + method: 'GET', + url: '/models', + }, + output: { + postReceive: [ + { + type: 'rootProperty', + properties: { + property: 'data', + }, + }, + { + type: 'filter', + properties: { + pass: '={{ /nemotron/i.test($responseItem.id) }}', + }, + }, + { + type: 'setKeyValue', + properties: { + name: '={{$responseItem.id}}', + value: '={{$responseItem.id}}', + }, + }, + { + type: 'sort', + properties: { + key: 'name', + }, + }, + ], + }, + }, + }, + additionalData, + PROVIDER_NODE_TYPE_MAP.nvidia, + {}, + credentials, + ); + } + private async fetchMistralCloudModels( credentials: INodeCredentials, additionalData: IWorkflowExecuteAdditionalData, diff --git a/packages/cli/src/modules/chat-hub/context-limits.ts b/packages/cli/src/modules/chat-hub/context-limits.ts index 6d41cdd8692..e6e2db35a03 100644 --- a/packages/cli/src/modules/chat-hub/context-limits.ts +++ b/packages/cli/src/modules/chat-hub/context-limits.ts @@ -214,6 +214,7 @@ export const maxContextWindowTokens: Record = { deepSeek: 'DeepSeek', cohere: 'Cohere', mistralCloud: 'Mistral Cloud', + nvidia: 'NVIDIA Nemotron', n8n: 'Workflow agent', 'custom-agent': 'Personal agent', };