mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-04 18:49:20 +02:00
feat(NVIDIA Nemotron Chat Model Node): Restrict model selector to supported models (#31698)
This commit is contained in:
parent
ab741ed6db
commit
ac4778bb5c
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user