mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(AI Transform Node): Node Prompt improvements (#11611)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
This commit is contained in:
parent
fcbf0ea771
commit
40a7445f08
|
@ -105,6 +105,7 @@ async function onSubmit() {
|
||||||
const updateInformation = await generateCodeForAiTransform(
|
const updateInformation = await generateCodeForAiTransform(
|
||||||
prompt.value,
|
prompt.value,
|
||||||
getPath(target as string),
|
getPath(target as string),
|
||||||
|
5,
|
||||||
);
|
);
|
||||||
if (!updateInformation) return;
|
if (!updateInformation) return;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { generateCodeForAiTransform } from './utils';
|
||||||
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
|
import { generateCodeForPrompt } from '@/api/ai';
|
||||||
|
|
||||||
|
vi.mock('./utils', async () => {
|
||||||
|
const actual = await vi.importActual('./utils');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
getSchemas: vi.fn(() => ({
|
||||||
|
parentNodesSchemas: { test: 'parentSchema' },
|
||||||
|
inputSchema: { test: 'inputSchema' },
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('@/stores/root.store', () => ({
|
||||||
|
useRootStore: () => ({
|
||||||
|
pushRef: 'mockRootPushRef',
|
||||||
|
restApiContext: {},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/stores/ndv.store', () => ({
|
||||||
|
useNDVStore: () => ({
|
||||||
|
pushRef: 'mockNdvPushRef',
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/stores/settings.store', () => ({
|
||||||
|
useSettingsStore: vi.fn(() => ({ settings: {}, isAskAiEnabled: true })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('prettier', () => ({
|
||||||
|
format: vi.fn(async (code) => await Promise.resolve(`formatted-${code}`)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('@/api/ai', () => ({
|
||||||
|
generateCodeForPrompt: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('generateCodeForAiTransform - Retry Tests', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
const pinia = createPinia();
|
||||||
|
setActivePinia(pinia);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retry and succeed on the second attempt', async () => {
|
||||||
|
const mockGeneratedCode = 'const example = "retry success";';
|
||||||
|
|
||||||
|
vi.mocked(generateCodeForPrompt)
|
||||||
|
.mockRejectedValueOnce(new Error('First attempt failed'))
|
||||||
|
.mockResolvedValueOnce({ code: mockGeneratedCode });
|
||||||
|
|
||||||
|
const result = await generateCodeForAiTransform('test prompt', 'test/path', 2);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
name: 'test/path',
|
||||||
|
value: 'formatted-const example = "retry success";',
|
||||||
|
});
|
||||||
|
expect(generateCodeForPrompt).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should exhaust retries and throw an error', async () => {
|
||||||
|
vi.mocked(generateCodeForPrompt).mockRejectedValue(new Error('All attempts failed'));
|
||||||
|
|
||||||
|
await expect(generateCodeForAiTransform('test prompt', 'test/path', 3)).rejects.toThrow(
|
||||||
|
'All attempts failed',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(generateCodeForPrompt).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should succeed on the first attempt without retries', async () => {
|
||||||
|
const mockGeneratedCode = 'const example = "no retries needed";';
|
||||||
|
vi.mocked(generateCodeForPrompt).mockResolvedValue({ code: mockGeneratedCode });
|
||||||
|
|
||||||
|
const result = await generateCodeForAiTransform('test prompt', 'test/path');
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
name: 'test/path',
|
||||||
|
value: 'formatted-const example = "no retries needed";',
|
||||||
|
});
|
||||||
|
expect(generateCodeForPrompt).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
|
@ -4,10 +4,10 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useDataSchema } from '@/composables/useDataSchema';
|
import { useDataSchema } from '@/composables/useDataSchema';
|
||||||
import { executionDataToJson } from '@/utils/nodeTypesUtils';
|
import { executionDataToJson } from '@/utils/nodeTypesUtils';
|
||||||
import { generateCodeForPrompt } from '../../api/ai';
|
import { generateCodeForPrompt } from '@/api/ai';
|
||||||
import { useRootStore } from '../../stores/root.store';
|
import { useRootStore } from '@/stores/root.store';
|
||||||
import { type AskAiRequest } from '../../types/assistant.types';
|
import { type AskAiRequest } from '@/types/assistant.types';
|
||||||
import { useSettingsStore } from '../../stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { format } from 'prettier';
|
import { format } from 'prettier';
|
||||||
import jsParser from 'prettier/plugins/babel';
|
import jsParser from 'prettier/plugins/babel';
|
||||||
import * as estree from 'prettier/plugins/estree';
|
import * as estree from 'prettier/plugins/estree';
|
||||||
|
@ -43,7 +43,7 @@ export function getSchemas() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodeName: node?.name || '',
|
nodeName: node?.name || '',
|
||||||
schema: getSchemaForExecutionData(executionDataToJson(inputData), true),
|
schema: getSchemaForExecutionData(executionDataToJson(inputData), false),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((node) => node.schema?.value.length > 0);
|
.filter((node) => node.schema?.value.length > 0);
|
||||||
|
@ -57,7 +57,7 @@ export function getSchemas() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateCodeForAiTransform(prompt: string, path: string) {
|
export async function generateCodeForAiTransform(prompt: string, path: string, retries = 1) {
|
||||||
const schemas = getSchemas();
|
const schemas = getSchemas();
|
||||||
|
|
||||||
const payload: AskAiRequest.RequestPayload = {
|
const payload: AskAiRequest.RequestPayload = {
|
||||||
|
@ -74,7 +74,20 @@ export async function generateCodeForAiTransform(prompt: string, path: string) {
|
||||||
let value;
|
let value;
|
||||||
if (useSettingsStore().isAskAiEnabled) {
|
if (useSettingsStore().isAskAiEnabled) {
|
||||||
const { restApiContext } = useRootStore();
|
const { restApiContext } = useRootStore();
|
||||||
const { code } = await generateCodeForPrompt(restApiContext, payload);
|
|
||||||
|
let code = '';
|
||||||
|
|
||||||
|
while (retries > 0) {
|
||||||
|
try {
|
||||||
|
const { code: generatedCode } = await generateCodeForPrompt(restApiContext, payload);
|
||||||
|
code = generatedCode;
|
||||||
|
break;
|
||||||
|
} catch (e) {
|
||||||
|
retries--;
|
||||||
|
if (!retries) throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
value = code;
|
value = code;
|
||||||
} else {
|
} else {
|
||||||
throw new ApplicationError('AI code generation is not enabled');
|
throw new ApplicationError('AI code generation is not enabled');
|
||||||
|
|
|
@ -287,6 +287,7 @@ async function onClick() {
|
||||||
const updateInformation = await generateCodeForAiTransform(
|
const updateInformation = await generateCodeForAiTransform(
|
||||||
prompt,
|
prompt,
|
||||||
`parameters.${AI_TRANSFORM_JS_CODE}`,
|
`parameters.${AI_TRANSFORM_JS_CODE}`,
|
||||||
|
5,
|
||||||
);
|
);
|
||||||
if (!updateInformation) return;
|
if (!updateInformation) return;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue