mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
feat: Add vector store telemetry events to Manual workflow exec finished event (no-changelog) (#16280)
This commit is contained in:
@@ -731,6 +731,7 @@ export class TelemetryEventRelay extends EventRelay {
|
|||||||
is_managed: false,
|
is_managed: false,
|
||||||
eval_rows_left: null,
|
eval_rows_left: null,
|
||||||
...TelemetryHelpers.resolveAIMetrics(workflow.nodes, this.nodeTypes),
|
...TelemetryHelpers.resolveAIMetrics(workflow.nodes, this.nodeTypes),
|
||||||
|
...TelemetryHelpers.resolveVectorStoreMetrics(workflow.nodes, this.nodeTypes, runData),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!manualExecEventProperties.node_graph_string) {
|
if (!manualExecEventProperties.node_graph_string) {
|
||||||
|
|||||||
@@ -604,3 +604,43 @@ export function resolveAIMetrics(nodes: INode[], nodeTypes: INodeTypes): FromAIC
|
|||||||
fromAIExpressionCount,
|
fromAIExpressionCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type VectorStoreMetrics = {
|
||||||
|
insertedIntoVectorStore: boolean;
|
||||||
|
queriedDataFromVectorStore: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resolveVectorStoreMetrics(
|
||||||
|
nodes: INode[],
|
||||||
|
nodeTypes: INodeTypes,
|
||||||
|
run: IRun,
|
||||||
|
): VectorStoreMetrics | {} {
|
||||||
|
const resolvedNodes = nodes
|
||||||
|
.map((x) => [x, nodeTypes.getByNameAndVersion(x.type, x.typeVersion)] as const)
|
||||||
|
.filter((x) => !!x[1]?.description);
|
||||||
|
|
||||||
|
const vectorStores = resolvedNodes.filter(
|
||||||
|
(x) =>
|
||||||
|
x[1].description.codex?.categories?.includes('AI') &&
|
||||||
|
x[1].description.codex?.subcategories?.AI?.includes('Vector Stores'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (vectorStores.length === 0) return {};
|
||||||
|
|
||||||
|
const runData = run?.data?.resultData?.runData;
|
||||||
|
const succeededVectorStores = vectorStores.filter((x) =>
|
||||||
|
runData?.[x[0].name]?.some((execution) => execution.executionStatus === 'success'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const insertingVectorStores = succeededVectorStores.filter(
|
||||||
|
(x) => x[0].parameters?.mode === 'insert',
|
||||||
|
);
|
||||||
|
const retrievingVectorStores = succeededVectorStores.filter((x) =>
|
||||||
|
['retrieve-as-tool', 'retrieve', 'load'].find((y) => y === x[0].parameters?.mode),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
insertedIntoVectorStore: insertingVectorStores.length > 0,
|
||||||
|
queriedDataFromVectorStore: retrievingVectorStores.length > 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
getDomainBase,
|
getDomainBase,
|
||||||
getDomainPath,
|
getDomainPath,
|
||||||
resolveAIMetrics,
|
resolveAIMetrics,
|
||||||
|
resolveVectorStoreMetrics,
|
||||||
userInInstanceRanOutOfFreeAiCredits,
|
userInInstanceRanOutOfFreeAiCredits,
|
||||||
} from '@/telemetry-helpers';
|
} from '@/telemetry-helpers';
|
||||||
import { randomInt } from '@/utils';
|
import { randomInt } from '@/utils';
|
||||||
@@ -1808,3 +1809,221 @@ describe('makeAIMetrics', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('resolveVectorStoreMetrics', () => {
|
||||||
|
const makeNode = (parameters: object, type: string) =>
|
||||||
|
({
|
||||||
|
parameters,
|
||||||
|
type,
|
||||||
|
typeVersion: 1,
|
||||||
|
id: '7cb0b373-715c-4a89-8bbb-3f238907bc86',
|
||||||
|
name: 'a name',
|
||||||
|
position: [0, 0],
|
||||||
|
}) as INode;
|
||||||
|
|
||||||
|
it('should return empty object if no vector store nodes are present', () => {
|
||||||
|
const nodes = [
|
||||||
|
makeNode(
|
||||||
|
{
|
||||||
|
mode: 'insert',
|
||||||
|
},
|
||||||
|
'n8n-nodes-base.nonVectorStoreNode',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeTypes = mock<NodeTypes>({
|
||||||
|
getByNameAndVersion: () => ({
|
||||||
|
description: {
|
||||||
|
codex: {
|
||||||
|
categories: ['Non-AI'],
|
||||||
|
},
|
||||||
|
} as unknown as INodeTypeDescription,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const run = mock<IRun>({
|
||||||
|
data: {
|
||||||
|
resultData: {
|
||||||
|
runData: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveVectorStoreMetrics(nodes, nodeTypes, run);
|
||||||
|
expect(result).toMatchObject({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect vector store nodes that inserted data', () => {
|
||||||
|
const nodes = [
|
||||||
|
makeNode(
|
||||||
|
{
|
||||||
|
mode: 'insert',
|
||||||
|
},
|
||||||
|
'n8n-nodes-base.vectorStoreNode',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeTypes = mock<NodeTypes>({
|
||||||
|
getByNameAndVersion: () => ({
|
||||||
|
description: {
|
||||||
|
codex: {
|
||||||
|
categories: ['AI'],
|
||||||
|
subcategories: { AI: ['Vector Stores'] },
|
||||||
|
},
|
||||||
|
} as unknown as INodeTypeDescription,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const run = mock<IRun>({
|
||||||
|
data: {
|
||||||
|
resultData: {
|
||||||
|
runData: {
|
||||||
|
'a name': [
|
||||||
|
{
|
||||||
|
executionStatus: 'success',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveVectorStoreMetrics(nodes, nodeTypes, run);
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
insertedIntoVectorStore: true,
|
||||||
|
queriedDataFromVectorStore: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect vector store nodes that queried data', () => {
|
||||||
|
const nodes = [
|
||||||
|
makeNode(
|
||||||
|
{
|
||||||
|
mode: 'retrieve',
|
||||||
|
},
|
||||||
|
'n8n-nodes-base.vectorStoreNode',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeTypes = mock<NodeTypes>({
|
||||||
|
getByNameAndVersion: () => ({
|
||||||
|
description: {
|
||||||
|
codex: {
|
||||||
|
categories: ['AI'],
|
||||||
|
subcategories: { AI: ['Vector Stores'] },
|
||||||
|
},
|
||||||
|
} as unknown as INodeTypeDescription,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const run = mock<IRun>({
|
||||||
|
data: {
|
||||||
|
resultData: {
|
||||||
|
runData: {
|
||||||
|
'a name': [
|
||||||
|
{
|
||||||
|
executionStatus: 'success',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveVectorStoreMetrics(nodes, nodeTypes, run);
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
insertedIntoVectorStore: false,
|
||||||
|
queriedDataFromVectorStore: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect vector store nodes that both inserted and queried data', () => {
|
||||||
|
const nodes = [
|
||||||
|
makeNode(
|
||||||
|
{
|
||||||
|
mode: 'insert',
|
||||||
|
},
|
||||||
|
'n8n-nodes-base.vectorStoreNode',
|
||||||
|
),
|
||||||
|
makeNode(
|
||||||
|
{
|
||||||
|
mode: 'retrieve',
|
||||||
|
},
|
||||||
|
'n8n-nodes-base.vectorStoreNode',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeTypes = mock<NodeTypes>({
|
||||||
|
getByNameAndVersion: () => ({
|
||||||
|
description: {
|
||||||
|
codex: {
|
||||||
|
categories: ['AI'],
|
||||||
|
subcategories: { AI: ['Vector Stores'] },
|
||||||
|
},
|
||||||
|
} as unknown as INodeTypeDescription,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const run = mock<IRun>({
|
||||||
|
data: {
|
||||||
|
resultData: {
|
||||||
|
runData: {
|
||||||
|
'a name': [
|
||||||
|
{
|
||||||
|
executionStatus: 'success',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveVectorStoreMetrics(nodes, nodeTypes, run);
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
insertedIntoVectorStore: true,
|
||||||
|
queriedDataFromVectorStore: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty object if no successful executions are found', () => {
|
||||||
|
const nodes = [
|
||||||
|
makeNode(
|
||||||
|
{
|
||||||
|
mode: 'insert',
|
||||||
|
},
|
||||||
|
'n8n-nodes-base.vectorStoreNode',
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeTypes = mock<NodeTypes>({
|
||||||
|
getByNameAndVersion: () => ({
|
||||||
|
description: {
|
||||||
|
codex: {
|
||||||
|
categories: ['AI'],
|
||||||
|
subcategories: { AI: ['Vector Stores'] },
|
||||||
|
},
|
||||||
|
} as unknown as INodeTypeDescription,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const run = mock<IRun>({
|
||||||
|
data: {
|
||||||
|
resultData: {
|
||||||
|
runData: {
|
||||||
|
'a name': [
|
||||||
|
{
|
||||||
|
executionStatus: 'error',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveVectorStoreMetrics(nodes, nodeTypes, run);
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
insertedIntoVectorStore: false,
|
||||||
|
queriedDataFromVectorStore: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user