feat(core): Implement wrapping of regular nodes as AI Tools (#10641)

Co-authored-by: JP van Oosten <jp@n8n.io>
This commit is contained in:
oleg
2024-09-04 12:06:17 +02:00
committed by GitHub
parent f114035a6b
commit da44fe4b89
13 changed files with 615 additions and 18 deletions

View File

@@ -13,11 +13,12 @@ import {
} from 'n8n-core';
import type {
KnownNodesAndCredentials,
INodeTypeBaseDescription,
INodeTypeDescription,
INodeTypeData,
ICredentialTypeData,
} from 'n8n-workflow';
import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
import { NodeHelpers, ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
import {
CUSTOM_API_CALL_KEY,
@@ -38,8 +39,11 @@ interface LoadedNodesAndCredentials {
export class LoadNodesAndCredentials {
private known: KnownNodesAndCredentials = { nodes: {}, credentials: {} };
// This contains the actually loaded objects, and their source paths
loaded: LoadedNodesAndCredentials = { nodes: {}, credentials: {} };
// For nodes, this only contains the descriptions, loaded from either the
// actual file, or the lazy loaded json
types: Types = { nodes: [], credentials: [] };
loaders: Record<string, DirectoryLoader> = {};
@@ -260,6 +264,34 @@ export class LoadNodesAndCredentials {
return loader;
}
/**
* This creates all AI Agent tools by duplicating the node descriptions for
* all nodes that are marked as `usableAsTool`. It basically modifies the
* description. The actual wrapping happens in the langchain code for getting
* the connected tools.
*/
createAiTools() {
const usableNodes: Array<INodeTypeBaseDescription | INodeTypeDescription> =
this.types.nodes.filter((nodetype) => nodetype.usableAsTool === true);
for (const usableNode of usableNodes) {
const description: INodeTypeBaseDescription | INodeTypeDescription =
structuredClone(usableNode);
const wrapped = NodeHelpers.convertNodeToAiTool({ description }).description;
this.types.nodes.push(wrapped);
this.known.nodes[wrapped.name] = structuredClone(this.known.nodes[usableNode.name]);
const credentialNames = Object.entries(this.known.credentials)
.filter(([_, credential]) => credential?.supportedNodes?.includes(usableNode.name))
.map(([credentialName]) => credentialName);
credentialNames.forEach((name) =>
this.known.credentials[name]?.supportedNodes?.push(wrapped.name),
);
}
}
async postProcessLoaders() {
this.known = { nodes: {}, credentials: {} };
this.loaded = { nodes: {}, credentials: {} };
@@ -307,6 +339,8 @@ export class LoadNodesAndCredentials {
}
}
this.createAiTools();
this.injectCustomApiCallOptions();
for (const postProcessor of this.postProcessors) {