mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Align DynamicStructuredTool and DynamicTool name fields (#14604)
feat(Code Tool Node): Use node's name instead of separate name field as tool name feat(Vector Store Tool Node): Use node's name instead of separate name field as tool name feat(Custom n8n Workflow Tool Node): Use node's name instead of separate name field as tool name
This commit is contained in:
@@ -599,7 +599,8 @@ describe('NDV', () => {
|
|||||||
cy.getByTestId(`add-subnode-${group.id}`).click();
|
cy.getByTestId(`add-subnode-${group.id}`).click();
|
||||||
|
|
||||||
cy.getByTestId('nodes-list-header').contains(group.title).should('exist');
|
cy.getByTestId('nodes-list-header').contains(group.title).should('exist');
|
||||||
nodeCreator.getters.getNthCreatorItem(1).click();
|
// Add HTTP Request tool
|
||||||
|
nodeCreator.getters.getNthCreatorItem(2).click();
|
||||||
getFloatingNodeByPosition('outputSub').should('exist');
|
getFloatingNodeByPosition('outputSub').should('exist');
|
||||||
getFloatingNodeByPosition('outputSub').click({ force: true });
|
getFloatingNodeByPosition('outputSub').click({ force: true });
|
||||||
|
|
||||||
@@ -610,7 +611,8 @@ describe('NDV', () => {
|
|||||||
// Expand the subgroup
|
// Expand the subgroup
|
||||||
cy.getByTestId('subnode-connection-group-ai_tool').click();
|
cy.getByTestId('subnode-connection-group-ai_tool').click();
|
||||||
cy.getByTestId(`add-subnode-${group.id}`).click();
|
cy.getByTestId(`add-subnode-${group.id}`).click();
|
||||||
nodeCreator.getters.getNthCreatorItem(1).click();
|
// Add HTTP Request tool
|
||||||
|
nodeCreator.getters.getNthCreatorItem(2).click();
|
||||||
getFloatingNodeByPosition('outputSub').click({ force: true });
|
getFloatingNodeByPosition('outputSub').click({ force: true });
|
||||||
cy.getByTestId('subnode-connection-group-ai_tool')
|
cy.getByTestId('subnode-connection-group-ai_tool')
|
||||||
.findChildByTestId('floating-subnode')
|
.findChildByTestId('floating-subnode')
|
||||||
@@ -619,7 +621,7 @@ describe('NDV', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Since language model has no credentials set, it should show an error
|
// Since language model has no credentials set, it should show an error
|
||||||
// Sinse code tool require alphanumeric tool name it would also show an error(2 errors, 1 for each tool node)
|
// Since HTTP Request tool requires URL it would also show an error(2 errors, 1 for each tool node)
|
||||||
cy.get('[class*=hasIssues]').should('have.length', 3);
|
cy.get('[class*=hasIssues]').should('have.length', 3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { DynamicTool } from 'langchain/tools';
|
||||||
|
import { type INode, type ISupplyDataFunctions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { ToolCode } from './ToolCode.node';
|
||||||
|
|
||||||
|
describe('ToolCode', () => {
|
||||||
|
describe('supplyData', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read name from node name on version >=1.2', async () => {
|
||||||
|
const node = new ToolCode();
|
||||||
|
|
||||||
|
const supplyDataResult = await node.supplyData.call(
|
||||||
|
mock<ISupplyDataFunctions>({
|
||||||
|
getNode: jest.fn(() => mock<INode>({ typeVersion: 1.2, name: 'test tool' })),
|
||||||
|
getNodeParameter: jest.fn().mockImplementation((paramName, _itemIndex) => {
|
||||||
|
switch (paramName) {
|
||||||
|
case 'description':
|
||||||
|
return 'description text';
|
||||||
|
case 'name':
|
||||||
|
return 'wrong_field';
|
||||||
|
case 'specifyInputSchema':
|
||||||
|
return false;
|
||||||
|
case 'language':
|
||||||
|
return 'javaScript';
|
||||||
|
case 'jsCode':
|
||||||
|
return 'return 1;';
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(supplyDataResult.response).toBeInstanceOf(DynamicTool);
|
||||||
|
|
||||||
|
const tool = supplyDataResult.response as DynamicTool;
|
||||||
|
expect(tool.name).toBe('test_tool');
|
||||||
|
expect(tool.description).toBe('description text');
|
||||||
|
expect(tool.func).toBeInstanceOf(Function);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read name from name parameter on version <1.2', async () => {
|
||||||
|
const node = new ToolCode();
|
||||||
|
|
||||||
|
const supplyDataResult = await node.supplyData.call(
|
||||||
|
mock<ISupplyDataFunctions>({
|
||||||
|
getNode: jest.fn(() => mock<INode>({ typeVersion: 1.1, name: 'wrong name' })),
|
||||||
|
getNodeParameter: jest.fn().mockImplementation((paramName, _itemIndex) => {
|
||||||
|
switch (paramName) {
|
||||||
|
case 'description':
|
||||||
|
return 'description text';
|
||||||
|
case 'name':
|
||||||
|
return 'test_tool';
|
||||||
|
case 'specifyInputSchema':
|
||||||
|
return false;
|
||||||
|
case 'language':
|
||||||
|
return 'javaScript';
|
||||||
|
case 'jsCode':
|
||||||
|
return 'return 1;';
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(supplyDataResult.response).toBeInstanceOf(DynamicTool);
|
||||||
|
|
||||||
|
const tool = supplyDataResult.response as DynamicTool;
|
||||||
|
expect(tool.name).toBe('test_tool');
|
||||||
|
expect(tool.description).toBe('description text');
|
||||||
|
expect(tool.func).toBeInstanceOf(Function);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
buildJsonSchemaExampleField,
|
buildJsonSchemaExampleField,
|
||||||
schemaTypeField,
|
schemaTypeField,
|
||||||
} from '@utils/descriptions';
|
} from '@utils/descriptions';
|
||||||
|
import { nodeNameToToolName } from '@utils/helpers';
|
||||||
import { convertJsonSchemaToZod, generateSchema } from '@utils/schemaParsing';
|
import { convertJsonSchemaToZod, generateSchema } from '@utils/schemaParsing';
|
||||||
import { getConnectionHintNoticeField } from '@utils/sharedFields';
|
import { getConnectionHintNoticeField } from '@utils/sharedFields';
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ export class ToolCode implements INodeType {
|
|||||||
icon: 'fa:code',
|
icon: 'fa:code',
|
||||||
iconColor: 'black',
|
iconColor: 'black',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: [1, 1.1],
|
version: [1, 1.1, 1.2],
|
||||||
description: 'Write a tool in JS or Python',
|
description: 'Write a tool in JS or Python',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Code Tool',
|
name: 'Code Tool',
|
||||||
@@ -88,7 +89,7 @@ export class ToolCode implements INodeType {
|
|||||||
'The name of the function to be called, could contain letters, numbers, and underscores only',
|
'The name of the function to be called, could contain letters, numbers, and underscores only',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
'@version': [{ _cnd: { gte: 1.1 } }],
|
'@version': [1.1],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -181,7 +182,12 @@ export class ToolCode implements INodeType {
|
|||||||
const node = this.getNode();
|
const node = this.getNode();
|
||||||
const workflowMode = this.getMode();
|
const workflowMode = this.getMode();
|
||||||
|
|
||||||
const name = this.getNodeParameter('name', itemIndex) as string;
|
const { typeVersion } = node;
|
||||||
|
const name =
|
||||||
|
typeVersion <= 1.1
|
||||||
|
? (this.getNodeParameter('name', itemIndex) as string)
|
||||||
|
: nodeNameToToolName(node);
|
||||||
|
|
||||||
const description = this.getNodeParameter('description', itemIndex) as string;
|
const description = this.getNodeParameter('description', itemIndex) as string;
|
||||||
|
|
||||||
const useSchema = this.getNodeParameter('specifyInputSchema', itemIndex) as boolean;
|
const useSchema = this.getNodeParameter('specifyInputSchema', itemIndex) as boolean;
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { VectorStoreQATool } from 'langchain/tools';
|
||||||
|
import { NodeConnectionTypes, type INode, type ISupplyDataFunctions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { ToolVectorStore } from './ToolVectorStore.node';
|
||||||
|
|
||||||
|
describe('ToolVectorStore', () => {
|
||||||
|
describe('supplyData', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read name from node name on version >=1.1', async () => {
|
||||||
|
const node = new ToolVectorStore();
|
||||||
|
|
||||||
|
const supplyDataResult = await node.supplyData.call(
|
||||||
|
mock<ISupplyDataFunctions>({
|
||||||
|
getNode: jest.fn(() => mock<INode>({ typeVersion: 1.2, name: 'test tool' })),
|
||||||
|
getNodeParameter: jest.fn().mockImplementation((paramName, _itemIndex) => {
|
||||||
|
switch (paramName) {
|
||||||
|
case 'name':
|
||||||
|
return 'wrong_field';
|
||||||
|
case 'topK':
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
getInputConnectionData: jest.fn().mockImplementation(async (inputName, _itemIndex) => {
|
||||||
|
switch (inputName) {
|
||||||
|
case NodeConnectionTypes.AiVectorStore:
|
||||||
|
return jest.fn();
|
||||||
|
case NodeConnectionTypes.AiLanguageModel:
|
||||||
|
return {
|
||||||
|
_modelType: jest.fn(),
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(supplyDataResult.response).toBeInstanceOf(VectorStoreQATool);
|
||||||
|
|
||||||
|
const tool = supplyDataResult.response as VectorStoreQATool;
|
||||||
|
expect(tool.name).toBe('test_tool');
|
||||||
|
expect(tool.description).toContain('test_tool');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read name from name parameter on version <1.2', async () => {
|
||||||
|
const node = new ToolVectorStore();
|
||||||
|
|
||||||
|
const supplyDataResult = await node.supplyData.call(
|
||||||
|
mock<ISupplyDataFunctions>({
|
||||||
|
getNode: jest.fn(() => mock<INode>({ typeVersion: 1, name: 'wrong name' })),
|
||||||
|
getNodeParameter: jest.fn().mockImplementation((paramName, _itemIndex) => {
|
||||||
|
switch (paramName) {
|
||||||
|
case 'name':
|
||||||
|
return 'test_tool';
|
||||||
|
case 'topK':
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
getInputConnectionData: jest.fn().mockImplementation(async (inputName, _itemIndex) => {
|
||||||
|
switch (inputName) {
|
||||||
|
case NodeConnectionTypes.AiVectorStore:
|
||||||
|
return jest.fn();
|
||||||
|
case NodeConnectionTypes.AiLanguageModel:
|
||||||
|
return {
|
||||||
|
_modelType: jest.fn(),
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(supplyDataResult.response).toBeInstanceOf(VectorStoreQATool);
|
||||||
|
|
||||||
|
const tool = supplyDataResult.response as VectorStoreQATool;
|
||||||
|
expect(tool.name).toBe('test_tool');
|
||||||
|
expect(tool.description).toContain('test_tool');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { nodeNameToToolName } from '@utils/helpers';
|
||||||
import { logWrapper } from '@utils/logWrapper';
|
import { logWrapper } from '@utils/logWrapper';
|
||||||
import { getConnectionHintNoticeField } from '@utils/sharedFields';
|
import { getConnectionHintNoticeField } from '@utils/sharedFields';
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ export class ToolVectorStore implements INodeType {
|
|||||||
icon: 'fa:database',
|
icon: 'fa:database',
|
||||||
iconColor: 'black',
|
iconColor: 'black',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: [1],
|
version: [1, 1.1],
|
||||||
description: 'Answer questions with a vector store',
|
description: 'Answer questions with a vector store',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: 'Answer questions with a vector store',
|
name: 'Answer questions with a vector store',
|
||||||
@@ -68,6 +69,11 @@ export class ToolVectorStore implements INodeType {
|
|||||||
validateType: 'string-alphanumeric',
|
validateType: 'string-alphanumeric',
|
||||||
description:
|
description:
|
||||||
'Name of the data in vector store. This will be used to fill this tool description: Useful for when you need to answer questions about [name]. Whenever you need information about [data description], you should ALWAYS use this. Input should be a fully formed question.',
|
'Name of the data in vector store. This will be used to fill this tool description: Useful for when you need to answer questions about [name]. Whenever you need information about [data description], you should ALWAYS use this. Input should be a fully formed question.',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'@version': [1],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Description of Data',
|
displayName: 'Description of Data',
|
||||||
@@ -92,7 +98,12 @@ export class ToolVectorStore implements INodeType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||||
const name = this.getNodeParameter('name', itemIndex) as string;
|
const node = this.getNode();
|
||||||
|
const { typeVersion } = node;
|
||||||
|
const name =
|
||||||
|
typeVersion <= 1
|
||||||
|
? (this.getNodeParameter('name', itemIndex) as string)
|
||||||
|
: nodeNameToToolName(node);
|
||||||
const toolDescription = this.getNodeParameter('description', itemIndex) as string;
|
const toolDescription = this.getNodeParameter('description', itemIndex) as string;
|
||||||
const topK = this.getNodeParameter('topK', itemIndex, 4) as number;
|
const topK = this.getNodeParameter('topK', itemIndex, 4) as number;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { DynamicTool } from 'langchain/tools';
|
||||||
|
import { type INode, type ISupplyDataFunctions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { ToolWorkflow } from './ToolWorkflow.node';
|
||||||
|
import type { ToolWorkflowV2 } from './v2/ToolWorkflowV2.node';
|
||||||
|
|
||||||
|
describe('ToolWorkflowV2', () => {
|
||||||
|
describe('supplyData', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read name from node name on version >=2.2', async () => {
|
||||||
|
const toolWorkflowNode = new ToolWorkflow();
|
||||||
|
const node = toolWorkflowNode.nodeVersions[2.2] as ToolWorkflowV2;
|
||||||
|
|
||||||
|
const supplyDataResult = await node.supplyData.call(
|
||||||
|
mock<ISupplyDataFunctions>({
|
||||||
|
getNode: jest.fn(() => mock<INode>({ typeVersion: 2.2, name: 'test tool' })),
|
||||||
|
getNodeParameter: jest.fn().mockImplementation((paramName, _itemIndex) => {
|
||||||
|
switch (paramName) {
|
||||||
|
case 'description':
|
||||||
|
return 'description text';
|
||||||
|
case 'name':
|
||||||
|
return 'wrong_field';
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(supplyDataResult.response).toBeInstanceOf(DynamicTool);
|
||||||
|
|
||||||
|
const tool = supplyDataResult.response as DynamicTool;
|
||||||
|
expect(tool.name).toBe('test_tool');
|
||||||
|
expect(tool.description).toBe('description text');
|
||||||
|
expect(tool.func).toBeInstanceOf(Function);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should read name from name parameter on version <2.2', async () => {
|
||||||
|
const toolWorkflowNode = new ToolWorkflow();
|
||||||
|
const node = toolWorkflowNode.nodeVersions[2.1] as ToolWorkflowV2;
|
||||||
|
|
||||||
|
const supplyDataResult = await node.supplyData.call(
|
||||||
|
mock<ISupplyDataFunctions>({
|
||||||
|
getNode: jest.fn(() => mock<INode>({ typeVersion: 2.1, name: 'wrong name' })),
|
||||||
|
getNodeParameter: jest.fn().mockImplementation((paramName, _itemIndex) => {
|
||||||
|
switch (paramName) {
|
||||||
|
case 'description':
|
||||||
|
return 'description text';
|
||||||
|
case 'name':
|
||||||
|
return 'test_tool';
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(supplyDataResult.response).toBeInstanceOf(DynamicTool);
|
||||||
|
|
||||||
|
const tool = supplyDataResult.response as DynamicTool;
|
||||||
|
expect(tool.name).toBe('test_tool');
|
||||||
|
expect(tool.description).toBe('description text');
|
||||||
|
expect(tool.func).toBeInstanceOf(Function);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -28,7 +28,7 @@ export class ToolWorkflow extends VersionedNodeType {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVersion: 2.1,
|
defaultVersion: 2.2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
||||||
@@ -38,6 +38,7 @@ export class ToolWorkflow extends VersionedNodeType {
|
|||||||
1.3: new ToolWorkflowV1(baseDescription),
|
1.3: new ToolWorkflowV1(baseDescription),
|
||||||
2: new ToolWorkflowV2(baseDescription),
|
2: new ToolWorkflowV2(baseDescription),
|
||||||
2.1: new ToolWorkflowV2(baseDescription),
|
2.1: new ToolWorkflowV2(baseDescription),
|
||||||
|
2.2: new ToolWorkflowV2(baseDescription),
|
||||||
};
|
};
|
||||||
super(nodeVersions, baseDescription);
|
super(nodeVersions, baseDescription);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import type {
|
|||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { nodeNameToToolName } from '@utils/helpers';
|
||||||
|
|
||||||
import { localResourceMapping } from './methods';
|
import { localResourceMapping } from './methods';
|
||||||
import { WorkflowToolService } from './utils/WorkflowToolService';
|
import { WorkflowToolService } from './utils/WorkflowToolService';
|
||||||
import { versionDescription } from './versionDescription';
|
import { versionDescription } from './versionDescription';
|
||||||
@@ -25,10 +27,15 @@ export class ToolWorkflowV2 implements INodeType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
|
||||||
const returnAllItems = this.getNode().typeVersion > 2;
|
const node = this.getNode();
|
||||||
|
const { typeVersion } = node;
|
||||||
|
const returnAllItems = typeVersion > 2;
|
||||||
|
|
||||||
const workflowToolService = new WorkflowToolService(this, { returnAllItems });
|
const workflowToolService = new WorkflowToolService(this, { returnAllItems });
|
||||||
const name = this.getNodeParameter('name', itemIndex) as string;
|
const name =
|
||||||
|
typeVersion <= 2.1
|
||||||
|
? (this.getNodeParameter('name', itemIndex) as string)
|
||||||
|
: nodeNameToToolName(node);
|
||||||
const description = this.getNodeParameter('description', itemIndex) as string;
|
const description = this.getNodeParameter('description', itemIndex) as string;
|
||||||
|
|
||||||
const tool = await workflowToolService.createTool({
|
const tool = await workflowToolService.createTool({
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const versionDescription: INodeTypeDescription = {
|
|||||||
defaults: {
|
defaults: {
|
||||||
name: 'Call n8n Workflow Tool',
|
name: 'Call n8n Workflow Tool',
|
||||||
},
|
},
|
||||||
version: [2, 2.1],
|
version: [2, 2.1, 2.2],
|
||||||
inputs: [],
|
inputs: [],
|
||||||
outputs: [NodeConnectionTypes.AiTool],
|
outputs: [NodeConnectionTypes.AiTool],
|
||||||
outputNames: ['Tool'],
|
outputNames: ['Tool'],
|
||||||
@@ -34,6 +34,11 @@ export const versionDescription: INodeTypeDescription = {
|
|||||||
validateType: 'string-alphanumeric',
|
validateType: 'string-alphanumeric',
|
||||||
description:
|
description:
|
||||||
'The name of the function to be called, could contain letters, numbers, and underscores only',
|
'The name of the function to be called, could contain letters, numbers, and underscores only',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'@version': [{ _cnd: { lte: 2.1 } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Description',
|
displayName: 'Description',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { BaseChatMemory } from 'langchain/memory';
|
|||||||
import { NodeConnectionTypes, NodeOperationError, jsonStringify } from 'n8n-workflow';
|
import { NodeConnectionTypes, NodeOperationError, jsonStringify } from 'n8n-workflow';
|
||||||
import type {
|
import type {
|
||||||
AiEvent,
|
AiEvent,
|
||||||
|
INode,
|
||||||
IDataObject,
|
IDataObject,
|
||||||
IExecuteFunctions,
|
IExecuteFunctions,
|
||||||
ISupplyDataFunctions,
|
ISupplyDataFunctions,
|
||||||
@@ -249,3 +250,7 @@ export function unwrapNestedOutput(output: Record<string, unknown>): Record<stri
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function nodeNameToToolName(node: INode): string {
|
||||||
|
return node.name.replace(/ /g, '_');
|
||||||
|
}
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ function makeDescription(node: INode, nodeType: INodeType): string {
|
|||||||
return nodeType.description.description;
|
return nodeType.description.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function nodeNameToToolName(node: INode): string {
|
||||||
|
return node.name.replace(/ /g, '_');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a DynamicStructuredTool from a node.
|
* Creates a DynamicStructuredTool from a node.
|
||||||
* @returns A DynamicStructuredTool instance.
|
* @returns A DynamicStructuredTool instance.
|
||||||
@@ -119,7 +123,7 @@ function createTool(options: CreateNodeAsToolOptions) {
|
|||||||
const { node, nodeType, handleToolInvocation } = options;
|
const { node, nodeType, handleToolInvocation } = options;
|
||||||
const schema = getSchema(node);
|
const schema = getSchema(node);
|
||||||
const description = makeDescription(node, nodeType);
|
const description = makeDescription(node, nodeType);
|
||||||
const nodeName = node.name.replace(/ /g, '_');
|
const nodeName = nodeNameToToolName(node);
|
||||||
const name = nodeName || nodeType.description.name;
|
const name = nodeName || nodeType.description.name;
|
||||||
|
|
||||||
return new DynamicStructuredTool({
|
return new DynamicStructuredTool({
|
||||||
|
|||||||
Reference in New Issue
Block a user