Skip to content

Commit

Permalink
feat: Add more AI node info to telemetry (#8827)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-radency committed Mar 7, 2024
1 parent 0f7ae3f commit ed6dc86
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 31 deletions.
3 changes: 3 additions & 0 deletions packages/editor-ui/src/views/NodeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,7 @@ export default defineComponent({
TelemetryHelpers.generateNodesGraph(
workflowData as IWorkflowBase,
this.workflowHelpers.getNodeTypes(),
{ isCloudDeployment: this.settingsStore.isCloudDeployment },
).nodeGraph,
),
};
Expand Down Expand Up @@ -1991,6 +1992,7 @@ export default defineComponent({
TelemetryHelpers.generateNodesGraph(
workflowData as IWorkflowBase,
this.workflowHelpers.getNodeTypes(),
{ isCloudDeployment: this.settingsStore.isCloudDeployment },
).nodeGraph,
),
};
Expand Down Expand Up @@ -2147,6 +2149,7 @@ export default defineComponent({
workflowData.meta && workflowData.meta.instanceId !== currInstanceId
? workflowData.meta.instanceId
: '',
isCloudDeployment: this.settingsStore.isCloudDeployment,
},
).nodeGraph,
);
Expand Down
50 changes: 43 additions & 7 deletions packages/workflow/src/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,54 @@ export const LOG_LEVELS = ['silent', 'error', 'warn', 'info', 'debug', 'verbose'
export const CODE_LANGUAGES = ['javaScript', 'python'] as const;
export const CODE_EXECUTION_MODES = ['runOnceForAllItems', 'runOnceForEachItem'] as const;

// Arbitrary value to represent an empty credential value
export const CREDENTIAL_EMPTY_VALUE =
'__n8n_EMPTY_VALUE_7b1af746-3729-4c60-9b9b-e08eb29e58da' as const;

export const FORM_TRIGGER_PATH_IDENTIFIER = 'n8n-form';

//n8n-nodes-base
export const STICKY_NODE_TYPE = 'n8n-nodes-base.stickyNote';
export const NO_OP_NODE_TYPE = 'n8n-nodes-base.noOp';
export const HTTP_REQUEST_NODE_TYPE = 'n8n-nodes-base.httpRequest';
export const WEBHOOK_NODE_TYPE = 'n8n-nodes-base.webhook';
export const MANUAL_TRIGGER_NODE_TYPE = 'n8n-nodes-base.manualTrigger';
export const ERROR_TRIGGER_NODE_TYPE = 'n8n-nodes-base.errorTrigger';
export const START_NODE_TYPE = 'n8n-nodes-base.start';
export const EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE = 'n8n-nodes-base.executeWorkflowTrigger';
export const CODE_NODE_TYPE = 'n8n-nodes-base.code';
export const FUNCTION_NODE_TYPE = 'n8n-nodes-base.function';
export const FUNCTION_ITEM_NODE_TYPE = 'n8n-nodes-base.functionItem';

export const STARTING_NODE_TYPES = [
MANUAL_TRIGGER_NODE_TYPE,
EXECUTE_WORKFLOW_TRIGGER_NODE_TYPE,
ERROR_TRIGGER_NODE_TYPE,
START_NODE_TYPE,
];

export const SCRIPTING_NODE_TYPES = [FUNCTION_NODE_TYPE, FUNCTION_ITEM_NODE_TYPE, CODE_NODE_TYPE];

/**
* Nodes whose parameter values may refer to other nodes without expressions.
* Their content may need to be updated when the referenced node is renamed.
*/
export const NODES_WITH_RENAMABLE_CONTENT = new Set([
'n8n-nodes-base.code',
'n8n-nodes-base.function',
'n8n-nodes-base.functionItem',
CODE_NODE_TYPE,
FUNCTION_NODE_TYPE,
FUNCTION_ITEM_NODE_TYPE,
]);

// Arbitrary value to represent an empty credential value
export const CREDENTIAL_EMPTY_VALUE =
'__n8n_EMPTY_VALUE_7b1af746-3729-4c60-9b9b-e08eb29e58da' as const;
//@n8n/n8n-nodes-langchain
export const MANUAL_CHAT_TRIGGER_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.manualChatTrigger';
export const AGENT_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.agent';
export const OPENAI_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.openAi';
export const CHAIN_SUMMARIZATION_LANGCHAIN_NODE_TYPE =
'@n8n/n8n-nodes-langchain.chainSummarization';
export const CODE_TOOL_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.toolCode';
export const WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE = '@n8n/n8n-nodes-langchain.toolWorkflow';

export const FORM_TRIGGER_PATH_IDENTIFIER = 'n8n-form';
export const LANGCHAIN_CUSTOM_TOOLS = [
CODE_TOOL_LANGCHAIN_NODE_TYPE,
WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE,
];
1 change: 1 addition & 0 deletions packages/workflow/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2238,6 +2238,7 @@ export interface INodeGraphItem {
src_node_id?: string;
src_instance_id?: string;
agent?: string; //@n8n/n8n-nodes-langchain.agent
prompts?: IDataObject[] | IDataObject; //ai node's prompts, cloud only
}

export interface INodeNameIndex {
Expand Down
73 changes: 67 additions & 6 deletions packages/workflow/src/TelemetryHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@ import type {
INodesGraphResult,
IWorkflowBase,
INodeTypes,
IDataObject,
} from './Interfaces';
import { ApplicationError } from './errors/application.error';

const STICKY_NODE_TYPE = 'n8n-nodes-base.stickyNote';
import {
AGENT_LANGCHAIN_NODE_TYPE,
CHAIN_SUMMARIZATION_LANGCHAIN_NODE_TYPE,
HTTP_REQUEST_NODE_TYPE,
LANGCHAIN_CUSTOM_TOOLS,
OPENAI_LANGCHAIN_NODE_TYPE,
STICKY_NODE_TYPE,
WEBHOOK_NODE_TYPE,
} from './Constants';

export function getNodeTypeForName(workflow: IWorkflowBase, nodeName: string): INode | undefined {
return workflow.nodes.find((node) => node.name === nodeName);
Expand Down Expand Up @@ -95,6 +103,7 @@ export function generateNodesGraph(
options?: {
sourceInstanceId?: string;
nodeIdMap?: { [curr: string]: string };
isCloudDeployment?: boolean;
},
): INodesGraphResult {
const nodeGraph: INodesGraph = {
Expand Down Expand Up @@ -158,15 +167,15 @@ export function generateNodesGraph(
nodeItem.src_node_id = options.nodeIdMap[node.id];
}

if (node.type === '@n8n/n8n-nodes-langchain.agent') {
if (node.type === AGENT_LANGCHAIN_NODE_TYPE) {
nodeItem.agent = (node.parameters.agent as string) || 'conversationalAgent';
} else if (node.type === 'n8n-nodes-base.httpRequest' && node.typeVersion === 1) {
} else if (node.type === HTTP_REQUEST_NODE_TYPE && node.typeVersion === 1) {
try {
nodeItem.domain = new URL(node.parameters.url as string).hostname;
} catch {
nodeItem.domain = getDomainBase(node.parameters.url as string);
}
} else if (node.type === 'n8n-nodes-base.httpRequest' && node.typeVersion > 1) {
} else if (node.type === HTTP_REQUEST_NODE_TYPE && node.typeVersion > 1) {
const { authentication } = node.parameters as { authentication: string };

nodeItem.credential_type = {
Expand All @@ -182,7 +191,7 @@ export function generateNodesGraph(
nodeItem.domain_base = getDomainBase(url);
nodeItem.domain_path = getDomainPath(url);
nodeItem.method = node.parameters.requestMethod as string;
} else if (node.type === 'n8n-nodes-base.webhook') {
} else if (node.type === WEBHOOK_NODE_TYPE) {
webhookNodeNames.push(node.name);
} else {
try {
Expand Down Expand Up @@ -216,6 +225,58 @@ export function generateNodesGraph(
}
}

if (options?.isCloudDeployment === true) {
if (node.type === OPENAI_LANGCHAIN_NODE_TYPE) {
nodeItem.prompts =
(((node.parameters?.messages as IDataObject) || {}).values as IDataObject[]) || [];
}

if (node.type === AGENT_LANGCHAIN_NODE_TYPE) {
const prompts: IDataObject = {};

if (node.parameters?.text) {
prompts.text = node.parameters.text as string;
}
const nodeOptions = node.parameters?.options as IDataObject;

if (nodeOptions) {
const optionalMessagesKeys = [
'humanMessage',
'systemMessage',
'humanMessageTemplate',
'prefix',
'suffixChat',
'suffix',
'prefixPrompt',
'suffixPrompt',
];

for (const key of optionalMessagesKeys) {
if (nodeOptions[key]) {
prompts[key] = nodeOptions[key] as string;
}
}
}

if (Object.keys(prompts).length) {
nodeItem.prompts = prompts;
}
}

if (node.type === CHAIN_SUMMARIZATION_LANGCHAIN_NODE_TYPE) {
nodeItem.prompts = (
(((node.parameters?.options as IDataObject) || {})
.summarizationMethodAndPrompts as IDataObject) || {}
).values as IDataObject;
}

if (LANGCHAIN_CUSTOM_TOOLS.includes(node.type)) {
nodeItem.prompts = {
description: (node.parameters?.description as string) || '',
};
}
}

nodeGraph.nodes[index.toString()] = nodeItem;
nameIndices[node.name] = index.toString();
});
Expand Down
19 changes: 8 additions & 11 deletions packages/workflow/src/Workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ import * as NodeHelpers from './NodeHelpers';
import * as ObservableObject from './ObservableObject';
import { RoutingNode } from './RoutingNode';
import { Expression } from './Expression';
import { NODES_WITH_RENAMABLE_CONTENT } from './Constants';
import {
MANUAL_CHAT_TRIGGER_LANGCHAIN_NODE_TYPE,
NODES_WITH_RENAMABLE_CONTENT,
STARTING_NODE_TYPES,
} from './Constants';
import { ApplicationError } from './errors/application.error';

function dedupe<T>(arr: T[]): T[] {
Expand Down Expand Up @@ -990,7 +994,7 @@ export class Workflow {
nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);

// TODO: Identify later differently
if (nodeType.description.name === '@n8n/n8n-nodes-langchain.manualChatTrigger') {
if (nodeType.description.name === MANUAL_CHAT_TRIGGER_LANGCHAIN_NODE_TYPE) {
continue;
}

Expand All @@ -1002,20 +1006,13 @@ export class Workflow {
}
}

const startingNodeTypes = [
'n8n-nodes-base.manualTrigger',
'n8n-nodes-base.executeWorkflowTrigger',
'n8n-nodes-base.errorTrigger',
'n8n-nodes-base.start',
];

const sortedNodeNames = Object.values(this.nodes)
.sort((a, b) => startingNodeTypes.indexOf(a.type) - startingNodeTypes.indexOf(b.type))
.sort((a, b) => STARTING_NODE_TYPES.indexOf(a.type) - STARTING_NODE_TYPES.indexOf(b.type))
.map((n) => n.name);

for (const nodeName of sortedNodeNames) {
node = this.nodes[nodeName];
if (startingNodeTypes.includes(node.type)) {
if (STARTING_NODE_TYPES.includes(node.type)) {
if (node.disabled === true) {
continue;
}
Expand Down
7 changes: 1 addition & 6 deletions packages/workflow/src/WorkflowDataProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,14 @@ import { augmentArray, augmentObject } from './AugmentObject';
import { deepCopy } from './utils';
import { getGlobalState } from './GlobalState';
import { ApplicationError } from './errors/application.error';
import { SCRIPTING_NODE_TYPES } from './Constants';

export function isResourceLocatorValue(value: unknown): value is INodeParameterResourceLocator {
return Boolean(
typeof value === 'object' && value && 'mode' in value && 'value' in value && '__rl' in value,
);
}

const SCRIPTING_NODE_TYPES = [
'n8n-nodes-base.function',
'n8n-nodes-base.functionItem',
'n8n-nodes-base.code',
];

const isScriptingNode = (nodeName: string, workflow: Workflow) => {
const node = workflow.getNode(nodeName);

Expand Down
3 changes: 2 additions & 1 deletion packages/workflow/src/errors/node-api.error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { NodeError } from './abstract/node.error';
import { removeCircularRefs } from '../utils';
import type { ReportingOptions } from './application.error';
import { AxiosError } from 'axios';
import { NO_OP_NODE_TYPE } from '../Constants';

export interface NodeOperationErrorOptions {
message?: string;
Expand Down Expand Up @@ -282,7 +283,7 @@ export class NodeApiError extends NodeError {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
this.message = this.message || this.description || UNKNOWN_ERROR_MESSAGE;
}
if (this.node.type === 'n8n-nodes-base.noOp' && this.message === UNKNOWN_ERROR_MESSAGE) {
if (this.node.type === NO_OP_NODE_TYPE && this.message === UNKNOWN_ERROR_MESSAGE) {
this.message = `${UNKNOWN_ERROR_MESSAGE_CRED} - ${this.httpCode}`;
}
}
Expand Down

0 comments on commit ed6dc86

Please sign in to comment.