Skip to content

Commit

Permalink
Address PR comments
Browse files Browse the repository at this point in the history
  • Loading branch information
OlegIvaniv committed Sep 3, 2024
1 parent 56389b6 commit 134f288
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 20 deletions.
2 changes: 1 addition & 1 deletion packages/cli/src/load-nodes-and-credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ export class LoadNodesAndCredentials {
const wrapped = NodeHelpers.convertNodeToAiTool({ description }).description;

this.types.nodes.push(wrapped);
this.known.nodes[wrapped.name] = this.known.nodes[usableNode.name];
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))
Expand Down
22 changes: 9 additions & 13 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 @@ -70,18 +78,6 @@ export class NodeTypes implements INodeTypes {
if (NodeHelpers.isINodeType(loaded)) NodeHelpers.applySpecialNodeParameters(loaded);

loadedNodes[type] = { sourcePath, type: loaded };

if (loaded.description.usableAsTool) {
const wrappedNode = NodeHelpers.convertNodeToAiTool(loaded);
if (!NodeHelpers.isINodeType(loaded) && !NodeHelpers.isINodeType(wrappedNode)) {
Object.entries(loaded.nodeVersions).forEach(([key, node]) => {
wrappedNode.nodeVersions[key as unknown as number] =
NodeHelpers.convertNodeToAiTool(node);
});
}
loadedNodes[type + 'Tool'] = { sourcePath, type: wrappedNode };
}

return loadedNodes[type];
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@
"typedi": "catalog:",
"uuid": "catalog:",
"xml2js": "catalog:",
"zod": "3.23.8"
"zod": "catalog:"
}
}
42 changes: 38 additions & 4 deletions packages/core/src/CreateNodeAsTool.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
/**
* @module NodeAsTool
* @description This module provides functionality to convert n8n nodes into LangChain tools.
* It includes utility functions for traversing complex objects, encoding/decoding dot notation,
* and creating dynamic structured tools from n8n nodes.
* @description This module converts n8n nodes into LangChain tools by analyzing node parameters,
* identifying placeholders, and generating a Zod schema. It then creates a DynamicStructuredTool
* that can be used in LangChain workflows.
*
* General approach:
* 1. Recursively traverse node parameters to find placeholders, including in nested structures
* 2. Generate a Zod schema based on these placeholders, preserving the nested structure
* 3. Create a DynamicStructuredTool with the schema and a function that executes the n8n node
*
* Example:
* - Node parameters:
* {
* "inputText": "{{ '__PLACEHOLDER: Enter main text to process' }}",
* "options": {
* "language": "{{ '__PLACEHOLDER: Specify language' }}",
* "advanced": {
* "maxLength": "{{ '__PLACEHOLDER: Enter maximum length' }}"
* }
* }
* }
*
* - Generated Zod schema:
* z.object({
* "inputText": z.string().describe("Enter main text to process"),
* "options__language": z.string().describe("Specify language"),
* "options__advanced__maxLength": z.string().describe("Enter maximum length")
* }).required()
*
* - Resulting tool can be called with:
* {
* "inputText": "Hello, world!",
* "options__language": "en",
* "options__advanced__maxLength": "100"
* }
*
* Note: Nested properties are flattened with double underscores in the schema,
* but the tool reconstructs the original nested structure when executing the node.
*/

import { DynamicStructuredTool } from '@langchain/core/tools';
Expand Down Expand Up @@ -166,7 +200,7 @@ export function createNodeAsTool(
const description = extractPlaceholderDescription(value);
schemaObj[key] = z.string().describe(description);
}
const schema = z.object(schemaObj);
const schema = z.object(schemaObj).required();

// Get the tool description from node parameters or use the default
const toolDescription = ctx.getNodeParameter(
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 134f288

Please sign in to comment.