feat(NVIDIA Nemotron Chat Model Node): Restrict model selector to supported models (#31698)

This commit is contained in:
Stephen Wright 2026-06-04 15:30:22 +01:00 committed by GitHub
parent ab741ed6db
commit ac4778bb5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 42 additions and 13 deletions

View File

@ -16,12 +16,18 @@ import {
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',
// Allow-list of supported Nemotron chat models. The selector fetches the live
// catalog from the API and reduces it to these ids; if the fetch fails, this
// same list is shown as the static fallback.
const NEMOTRON_SUPPORTED_MODELS = [
'nvidia/llama-3.1-nemotron-nano-8b-v1',
'nvidia/nemotron-4-340b-instruct',
'nvidia/nemotron-mini-4b-instruct',
'nvidia/llama-3.3-nemotron-super-49b-v1',
'nvidia/llama-3.3-nemotron-super-49b-v1.5',
'nvidia/nemotron-3-nano-30b-a3b',
'nvidia/nemotron-3-nano-omni-30b-a3b-reasoning',
'nvidia/nemotron-3-super-120b-a12b',
'nvidia/nemotron-nano-12b-v2-vl',
'nvidia/nvidia-nemotron-nano-9b-v2',
];
export class LmChatNvidia implements INodeType {
@ -103,7 +109,7 @@ export class LmChatNvidia implements INodeType {
{
type: 'filter',
properties: {
pass: '={{ /nemotron/i.test($responseItem.id) }}',
pass: `={{ ${JSON.stringify(NEMOTRON_SUPPORTED_MODELS)}.includes($responseItem.id) }}`,
},
},
{
@ -131,7 +137,7 @@ export class LmChatNvidia implements INodeType {
},
},
default: 'nvidia/llama-3.3-nemotron-super-49b-v1',
options: NEMOTRON_FALLBACK_MODELS.map((id) => ({ name: id, value: id })),
options: NEMOTRON_SUPPORTED_MODELS.map((id) => ({ name: id, value: id })),
},
{
displayName: 'Options',

View File

@ -46,7 +46,7 @@ describe('LmChatNvidia', () => {
});
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 === 'model') return 'nvidia/llama-3.3-nemotron-super-49b-v1';
if (paramName === 'options') return {};
return undefined;
});
@ -80,13 +80,36 @@ describe('LmChatNvidia', () => {
expect(node.description.outputNames).toEqual(['Model']);
});
it('should filter to Nemotron models in loadOptions', () => {
it('should reduce the fetched catalog to the supported allow-list 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);
const pass = filterStep?.properties.pass ?? '';
// The filter keeps only ids present in the allow-list (built from the same
// constant that backs the static options), excluding e.g. reward models.
const supported = ((modelProp as any)?.options as Array<{ value: string }>).map(
(o) => o.value,
);
expect(pass).toContain('.includes($responseItem.id)');
for (const id of supported) {
expect(pass).toContain(id);
}
expect(pass).not.toContain('nemotron-70b-reward');
});
it('should expose the supported models as static fallback options', () => {
const modelProp = node.description.properties.find((p) => p?.name === 'model');
const options = (modelProp as any)?.options as Array<{ name: string; value: string }>;
const values = options.map((o) => o.value);
expect(values).toContain('nvidia/llama-3.3-nemotron-super-49b-v1');
expect(values).toContain('nvidia/nemotron-3-super-120b-a12b');
expect(values).not.toContain('nvidia/llama-3.1-nemotron-70b-reward');
// The default must be one of the offered options.
expect(values).toContain((modelProp as any)?.default);
});
});
@ -100,7 +123,7 @@ describe('LmChatNvidia', () => {
expect(MockedChatOpenAI).toHaveBeenCalledWith(
expect.objectContaining({
apiKey: 'test-key',
model: 'nvidia/llama-3.1-nemotron-70b-instruct',
model: 'nvidia/llama-3.3-nemotron-super-49b-v1',
configuration: expect.objectContaining({
baseURL: 'https://integrate.api.nvidia.com/v1',
}),
@ -138,7 +161,7 @@ describe('LmChatNvidia', () => {
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 === 'model') return 'nvidia/llama-3.3-nemotron-super-49b-v1';
if (paramName === 'options')
return {
temperature: 0.5,
@ -170,7 +193,7 @@ describe('LmChatNvidia', () => {
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 === 'model') return 'nvidia/llama-3.3-nemotron-super-49b-v1';
if (paramName === 'options') return { responseFormat: 'json_object' };
return undefined;
});