feat(core): Expose $agentInfo variable for easy AI workflow (no-changelog) (#14445)

Co-authored-by: Mutasem Aldmour <mutasem@n8n.io>
This commit is contained in:
Charlie Kolb
2025-04-10 13:10:26 +02:00
committed by GitHub
parent d24b684a95
commit 3db47504a2
5 changed files with 466 additions and 18 deletions

View File

@@ -6,29 +6,30 @@ import * as jmespath from 'jmespath';
import { DateTime, Duration, Interval, Settings } from 'luxon';
import { augmentArray, augmentObject } from './AugmentObject';
import { SCRIPTING_NODE_TYPES } from './Constants';
import { AGENT_LANGCHAIN_NODE_TYPE, SCRIPTING_NODE_TYPES } from './Constants';
import { ApplicationError } from './errors/application.error';
import { ExpressionError, type ExpressionErrorOptions } from './errors/expression.error';
import { getGlobalState } from './GlobalState';
import {
type IDataObject,
type IExecuteData,
type INodeExecutionData,
type INodeParameters,
type IPairedItemData,
type IRunExecutionData,
type ISourceData,
type ITaskData,
type IWorkflowDataProxyAdditionalKeys,
type IWorkflowDataProxyData,
type INodeParameterResourceLocator,
type NodeParameterValueType,
type WorkflowExecuteMode,
type ProxyInput,
NodeConnectionTypes,
import { NodeConnectionTypes } from './Interfaces';
import type {
IDataObject,
IExecuteData,
INodeExecutionData,
INodeParameters,
IPairedItemData,
IRunExecutionData,
ISourceData,
ITaskData,
IWorkflowDataProxyAdditionalKeys,
IWorkflowDataProxyData,
INodeParameterResourceLocator,
NodeParameterValueType,
WorkflowExecuteMode,
ProxyInput,
INode,
} from './Interfaces';
import * as NodeHelpers from './NodeHelpers';
import { deepCopy } from './utils';
import { deepCopy, isObjectEmpty } from './utils';
import type { Workflow } from './Workflow';
import type { EnvProviderState } from './WorkflowDataProxyEnvProvider';
import { createEnvProvider, createEnvProviderState } from './WorkflowDataProxyEnvProvider';
@@ -159,6 +160,89 @@ export class WorkflowDataProxy {
);
}
private buildAgentToolInfo(node: INode) {
const nodeType = this.workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
const type = nodeType.description.displayName;
const params = NodeHelpers.getNodeParameters(
nodeType.description.properties,
node.parameters,
true,
false,
node,
nodeType.description,
);
const resourceKey = params?.resource;
const operationKey = params?.operation;
const resource =
nodeType.description.properties
.find((nodeProperties) => nodeProperties.name === 'resource')
?.options?.find((option) => 'value' in option && option.value === resourceKey)?.name ??
null;
const operation =
nodeType.description.properties
.find(
(nodeProperty) =>
nodeProperty.name === 'operation' &&
nodeProperty.displayOptions?.show?.resource?.some((y) => y === resourceKey),
)
?.options?.find((y) => 'value' in y && y.value === operationKey)?.name ?? null;
const hasCredentials = !isObjectEmpty(node.credentials ?? {});
const hasValidCalendar = nodeType.description.name.includes('googleCalendar')
? isResourceLocatorValue(node.parameters.calendar) && node.parameters.calendar.value !== ''
: undefined;
const aiDefinedFields = Object.entries(node.parameters)
.map(([key, value]) => [key, isResourceLocatorValue(value) ? value.value : value] as const)
.filter(([_, value]) => value?.toString().toLowerCase().includes('$fromai'))
.map(
([key]) =>
nodeType.description.properties.find((property) => property.name === key)?.displayName,
);
return {
name: node.name,
type,
resource,
operation,
hasCredentials,
hasValidCalendar,
aiDefinedFields,
};
}
private agentInfo() {
const agentNode = this.workflow.getNode(this.activeNodeName);
if (!agentNode || agentNode.type !== AGENT_LANGCHAIN_NODE_TYPE) return undefined;
const connectedTools = this.workflow
.getParentNodes(this.activeNodeName, NodeConnectionTypes.AiTool)
.map((nodeName) => this.workflow.getNode(nodeName))
.filter((node) => node) as INode[];
const memoryConnectedToAgent =
this.workflow.getParentNodes(this.activeNodeName, NodeConnectionTypes.AiMemory).length > 0;
const allTools = this.workflow.queryNodes((nodeType) => {
return nodeType.description.name.toLowerCase().includes('tool');
});
const unconnectedTools = allTools
.filter(
(node) =>
this.workflow.getChildNodes(node.name, NodeConnectionTypes.AiTool, 1).length === 0,
)
.filter((node) => !connectedTools.includes(node));
return {
memoryConnectedToAgent,
tools: [
...connectedTools.map((node) => ({ connected: true, ...this.buildAgentToolInfo(node) })),
...unconnectedTools.map((node) => ({ connected: false, ...this.buildAgentToolInfo(node) })),
],
};
}
/**
* Returns a proxy which allows to query parameter data of a given node
*
@@ -1394,6 +1478,7 @@ export class WorkflowDataProxy {
$thisRunIndex: this.runIndex,
$nodeVersion: that.workflow.getNode(that.activeNodeName)?.typeVersion,
$nodeId: that.workflow.getNode(that.activeNodeName)?.id,
$agentInfo: this.agentInfo(),
$webhookId: that.workflow.getNode(that.activeNodeName)?.webhookId,
};
const throwOnMissingExecutionData = opts?.throwOnMissingExecutionData ?? true;