feat: Add more AI node info to telemetry (#8827)

This commit is contained in:
Michael Kret 2024-03-07 12:46:07 +02:00 committed by GitHub
parent 0f7ae3f50a
commit ed6dc86d60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 128 additions and 34 deletions

View file

@ -1083,6 +1083,7 @@ export default defineComponent({
TelemetryHelpers.generateNodesGraph(
workflowData as IWorkflowBase,
this.workflowHelpers.getNodeTypes(),
{ isCloudDeployment: this.settingsStore.isCloudDeployment },
).nodeGraph,
),
};
@ -1991,6 +1992,7 @@ export default defineComponent({
TelemetryHelpers.generateNodesGraph(
workflowData as IWorkflowBase,
this.workflowHelpers.getNodeTypes(),
{ isCloudDeployment: this.settingsStore.isCloudDeployment },
).nodeGraph,
),
};
@ -2147,6 +2149,7 @@ export default defineComponent({
workflowData.meta && workflowData.meta.instanceId !== currInstanceId
? workflowData.meta.instanceId
: '',
isCloudDeployment: this.settingsStore.isCloudDeployment,
},
).nodeGraph,
);

View file

@ -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;
/**
* 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',
]);
// 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([
CODE_NODE_TYPE,
FUNCTION_NODE_TYPE,
FUNCTION_ITEM_NODE_TYPE,
]);
//@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 LANGCHAIN_CUSTOM_TOOLS = [
CODE_TOOL_LANGCHAIN_NODE_TYPE,
WORKFLOW_TOOL_LANGCHAIN_NODE_TYPE,
];

View file

@ -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 {

View file

@ -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);
@ -95,6 +103,7 @@ export function generateNodesGraph(
options?: {
sourceInstanceId?: string;
nodeIdMap?: { [curr: string]: string };
isCloudDeployment?: boolean;
},
): INodesGraphResult {
const nodeGraph: INodesGraph = {
@ -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 = {
@ -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 {
@ -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();
});

View file

@ -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[] {
@ -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;
}
@ -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;
}

View file

@ -28,6 +28,7 @@ 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(
@ -35,12 +36,6 @@ export function isResourceLocatorValue(value: unknown): value is INodeParameterR
);
}
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);

View file

@ -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;
@ -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}`;
}
}