fix(editor): Fix node graph generation for evaluation node in set metrics mode (#18344)

This commit is contained in:
oleg
2025-08-14 14:50:10 +02:00
committed by GitHub
parent dbbf1ba7f1
commit 8442382471
4 changed files with 245 additions and 63 deletions

View File

@@ -42,6 +42,7 @@ import type {
import { NodeConnectionTypes } from './interfaces';
import { getNodeParameters } from './node-helpers';
import { jsonParse } from './utils';
import { DEFAULT_EVALUATION_METRIC } from './evaluation-helpers';
const isNodeApiError = (error: unknown): error is NodeApiError =>
typeof error === 'object' && error !== null && 'name' in error && error?.name === 'NodeApiError';
@@ -414,11 +415,18 @@ export function generateNodesGraph(
options?.isCloudDeployment &&
node.parameters?.operation === 'setMetrics'
) {
const metrics = node.parameters?.metrics as IDataObject;
const metrics = node.parameters?.metrics as IDataObject | undefined;
nodeItem.metric_names = (metrics.assignments as Array<{ name: string }> | undefined)?.map(
(metric: { name: string }) => metric.name,
);
// If metrics are not defined, it means the node is using preconfigured metric
if (!metrics) {
const predefinedMetricKey =
(node.parameters?.metric as string | undefined) ?? DEFAULT_EVALUATION_METRIC;
nodeItem.metric_names = [predefinedMetricKey];
} else {
nodeItem.metric_names = (metrics.assignments as Array<{ name: string }> | undefined)?.map(
(metric: { name: string }) => metric.name,
);
}
} else if (node.type === CODE_NODE_TYPE) {
const { language } = node.parameters;
nodeItem.language =

View File

@@ -28,6 +28,7 @@ import {
userInInstanceRanOutOfFreeAiCredits,
} from '../src/telemetry-helpers';
import { randomInt } from '../src/utils';
import { DEFAULT_EVALUATION_METRIC } from '../src/evaluation-helpers';
describe('getDomainBase should return protocol plus domain', () => {
test('in valid URLs', () => {
@@ -1418,6 +1419,136 @@ describe('generateNodesGraph', () => {
evaluationTriggerNodeNames: [],
});
});
test('should handle Evaluation node with undefined metrics - uses default predefined metric', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {
operation: 'setMetrics',
// metrics is undefined - should fall back to default metric
},
id: 'eval-node-id',
name: 'Evaluation Node',
type: 'n8n-nodes-base.evaluation',
typeVersion: 1,
position: [100, 100],
},
],
connections: {},
pinData: {},
};
expect(generateNodesGraph(workflow, nodeTypes, { isCloudDeployment: true })).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.evaluation'],
node_connections: [],
nodes: {
'0': {
id: 'eval-node-id',
type: 'n8n-nodes-base.evaluation',
version: 1,
position: [100, 100],
metric_names: [DEFAULT_EVALUATION_METRIC], // Default metric
},
},
notes: {},
is_pinned: false,
},
nameIndices: { 'Evaluation Node': '0' },
webhookNodeNames: [],
evaluationTriggerNodeNames: [],
});
});
test('should handle Evaluation node with custom metric parameter', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {
operation: 'setMetrics',
metric: 'helpfulness',
// metrics is undefined but metric parameter is set
},
id: 'eval-node-id',
name: 'Evaluation Node',
type: 'n8n-nodes-base.evaluation',
typeVersion: 1,
position: [100, 100],
},
],
connections: {},
pinData: {},
};
expect(generateNodesGraph(workflow, nodeTypes, { isCloudDeployment: true })).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.evaluation'],
node_connections: [],
nodes: {
'0': {
id: 'eval-node-id',
type: 'n8n-nodes-base.evaluation',
version: 1,
position: [100, 100],
metric_names: ['helpfulness'], // Custom metric from parameter
},
},
notes: {},
is_pinned: false,
},
nameIndices: { 'Evaluation Node': '0' },
webhookNodeNames: [],
evaluationTriggerNodeNames: [],
});
});
test('should handle Evaluation node with valid metrics assignments', () => {
const workflow: Partial<IWorkflowBase> = {
nodes: [
{
parameters: {
operation: 'setMetrics',
metrics: {
assignments: [
{ name: 'accuracy', value: 0.95 },
{ name: 'precision', value: 0.87 },
{ name: 'recall', value: 0.92 },
],
},
},
id: 'eval-node-id',
name: 'Evaluation Node',
type: 'n8n-nodes-base.evaluation',
typeVersion: 1,
position: [100, 100],
},
],
connections: {},
pinData: {},
};
expect(generateNodesGraph(workflow, nodeTypes, { isCloudDeployment: true })).toEqual({
nodeGraph: {
node_types: ['n8n-nodes-base.evaluation'],
node_connections: [],
nodes: {
'0': {
id: 'eval-node-id',
type: 'n8n-nodes-base.evaluation',
version: 1,
position: [100, 100],
metric_names: ['accuracy', 'precision', 'recall'],
},
},
notes: {},
is_pinned: false,
},
nameIndices: { 'Evaluation Node': '0' },
webhookNodeNames: [],
evaluationTriggerNodeNames: [],
});
});
});
describe('extractLastExecutedNodeCredentialData', () => {