Skip to content

Commit

Permalink
feat(core): Implement wrapping of regular nodes as AI Tools (#10641)
Browse files Browse the repository at this point in the history
Co-authored-by: JP van Oosten <jp@n8n.io>
  • Loading branch information
OlegIvaniv and jeanpaul authored Sep 4, 2024
1 parent f114035 commit da44fe4
Show file tree
Hide file tree
Showing 13 changed files with 615 additions and 18 deletions.
2 changes: 1 addition & 1 deletion packages/@n8n/nodes-langchain/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { NodeConnectionType, NodeOperationError, jsonStringify } from 'n8n-workflow';
import type {
EventNamesAiNodesType,
IDataObject,
IExecuteFunctions,
IWebhookFunctions,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError, jsonStringify } from 'n8n-workflow';
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
import type { BaseOutputParser } from '@langchain/core/output_parsers';
import type { BaseMessage } from '@langchain/core/messages';
Expand Down
36 changes: 35 additions & 1 deletion packages/cli/src/load-nodes-and-credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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> = {};
Expand Down Expand Up @@ -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: {} };
Expand Down Expand Up @@ -307,6 +339,8 @@ export class LoadNodesAndCredentials {
}
}

this.createAiTools();

this.injectCustomApiCallOptions();

for (const postProcessor of this.postProcessors) {
Expand Down
15 changes: 12 additions & 3 deletions packages/cli/src/node-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ export class NodeTypes implements INodeTypes {
}

getByNameAndVersion(nodeType: string, version?: number): INodeType {
return NodeHelpers.getVersionedNodeType(this.getNode(nodeType).type, version);
const versionedNodeType = NodeHelpers.getVersionedNodeType(
this.getNode(nodeType).type,
version,
);
if (versionedNodeType.description.usableAsTool) {
return NodeHelpers.convertNodeToAiTool(versionedNodeType);
}

return versionedNodeType;
}

/* Some nodeTypes need to get special parameters applied like the polling nodes the polling times */
Expand All @@ -66,8 +74,9 @@ export class NodeTypes implements INodeTypes {

if (type in knownNodes) {
const { className, sourcePath } = knownNodes[type];
const loaded: INodeType = loadClassInIsolation(sourcePath, className);
NodeHelpers.applySpecialNodeParameters(loaded);
const loaded: INodeType | IVersionedNodeType = loadClassInIsolation(sourcePath, className);
if (NodeHelpers.isINodeType(loaded)) NodeHelpers.applySpecialNodeParameters(loaded);

loadedNodes[type] = { sourcePath, type: loaded };
return loadedNodes[type];
}
Expand Down
4 changes: 3 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"file-type": "16.5.4",
"form-data": "catalog:",
"lodash": "catalog:",
"@langchain/core": "0.2.18",
"luxon": "catalog:",
"mime-types": "2.1.35",
"n8n-workflow": "workspace:*",
Expand All @@ -54,6 +55,7 @@
"ssh2": "1.15.0",
"typedi": "catalog:",
"uuid": "catalog:",
"xml2js": "catalog:"
"xml2js": "catalog:",
"zod": "catalog:"
}
}
Loading

0 comments on commit da44fe4

Please sign in to comment.