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(
|
||||
prompt.value,
|
||||
getPath(target as string),
|
||||
5,
|
||||
);
|
||||
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 { useDataSchema } from '@/composables/useDataSchema';
|
||||
import { executionDataToJson } from '@/utils/nodeTypesUtils';
|
||||
import { generateCodeForPrompt } from '../../api/ai';
|
||||
import { useRootStore } from '../../stores/root.store';
|
||||
import { type AskAiRequest } from '../../types/assistant.types';
|
||||
import { useSettingsStore } from '../../stores/settings.store';
|
||||
import { generateCodeForPrompt } from '@/api/ai';
|
||||
import { useRootStore } from '@/stores/root.store';
|
||||
import { type AskAiRequest } from '@/types/assistant.types';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { format } from 'prettier';
|
||||
import jsParser from 'prettier/plugins/babel';
|
||||
import * as estree from 'prettier/plugins/estree';
|
||||
|
@ -43,7 +43,7 @@ export function getSchemas() {
|
|||
|
||||
return {
|
||||
nodeName: node?.name || '',
|
||||
schema: getSchemaForExecutionData(executionDataToJson(inputData), true),
|
||||
schema: getSchemaForExecutionData(executionDataToJson(inputData), false),
|
||||
};
|
||||
})
|
||||
.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 payload: AskAiRequest.RequestPayload = {
|
||||
|
@ -74,7 +74,20 @@ export async function generateCodeForAiTransform(prompt: string, path: string) {
|
|||
let value;
|
||||
if (useSettingsStore().isAskAiEnabled) {
|
||||
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;
|
||||
} else {
|
||||
throw new ApplicationError('AI code generation is not enabled');
|
||||
|
|
|
@ -287,6 +287,7 @@ async function onClick() {
|
|||
const updateInformation = await generateCodeForAiTransform(
|
||||
prompt,
|
||||
`parameters.${AI_TRANSFORM_JS_CODE}`,
|
||||
5,
|
||||
);
|
||||
if (!updateInformation) return;
|
||||
|
||||
|
|
Loading…
Reference in a new issue