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:
92
packages/core/test/CreateNodeAsTool.test.ts
Normal file
92
packages/core/test/CreateNodeAsTool.test.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { createNodeAsTool } from '@/CreateNodeAsTool';
|
||||
import type { IExecuteFunctions, INodeParameters, INodeType } from 'n8n-workflow';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
jest.mock('@langchain/core/tools', () => ({
|
||||
DynamicStructuredTool: jest.fn().mockImplementation((config) => ({
|
||||
name: config.name,
|
||||
description: config.description,
|
||||
schema: config.schema,
|
||||
func: config.func,
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('createNodeAsTool', () => {
|
||||
let mockCtx: IExecuteFunctions;
|
||||
let mockNode: INodeType;
|
||||
let mockNodeParameters: INodeParameters;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCtx = {
|
||||
getNodeParameter: jest.fn(),
|
||||
addInputData: jest.fn(),
|
||||
addOutputData: jest.fn(),
|
||||
} as unknown as IExecuteFunctions;
|
||||
|
||||
mockNode = {
|
||||
description: {
|
||||
name: 'TestNode',
|
||||
description: 'Test node description',
|
||||
},
|
||||
execute: jest.fn().mockResolvedValue([[{ json: { result: 'test' } }]]),
|
||||
} as unknown as INodeType;
|
||||
|
||||
mockNodeParameters = {
|
||||
param1: "{{ '__PLACEHOLDER: Test parameter' }}",
|
||||
param2: 'static value',
|
||||
nestedParam: {
|
||||
subParam: "{{ '__PLACEHOLDER: Nested parameter' }}",
|
||||
},
|
||||
};
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should create a DynamicStructuredTool with correct properties', () => {
|
||||
const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
|
||||
|
||||
expect(tool).toBeDefined();
|
||||
expect(tool.name).toBe('TestNode');
|
||||
expect(tool.description).toBe('Test node description');
|
||||
expect(tool.schema).toBeDefined();
|
||||
});
|
||||
|
||||
it('should use toolDescription if provided', () => {
|
||||
const customDescription = 'Custom tool description';
|
||||
(mockCtx.getNodeParameter as jest.Mock).mockReturnValue(customDescription);
|
||||
|
||||
const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
|
||||
|
||||
expect(tool.description).toBe(customDescription);
|
||||
});
|
||||
|
||||
it('should create a schema based on placeholder values in nodeParameters', () => {
|
||||
const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
|
||||
|
||||
expect(tool.schema).toBeDefined();
|
||||
expect(tool.schema.shape).toHaveProperty('param1');
|
||||
expect(tool.schema.shape).toHaveProperty('nestedParam__subParam');
|
||||
expect(tool.schema.shape).not.toHaveProperty('param2');
|
||||
});
|
||||
|
||||
it('should handle nested parameters correctly', () => {
|
||||
const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
|
||||
|
||||
expect(tool.schema.shape.nestedParam__subParam).toBeInstanceOf(z.ZodString);
|
||||
});
|
||||
|
||||
it('should create a function that wraps the node execution', async () => {
|
||||
const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
|
||||
|
||||
const result = await tool.func({ param1: 'test value', nestedParam__subParam: 'nested value' });
|
||||
|
||||
expect(mockCtx.addInputData).toHaveBeenCalledWith(NodeConnectionType.AiTool, [
|
||||
[{ json: { param1: 'test value', nestedParam__subParam: 'nested value' } }],
|
||||
]);
|
||||
expect(mockNode.execute).toHaveBeenCalled();
|
||||
expect(mockCtx.addOutputData).toHaveBeenCalledWith(NodeConnectionType.AiTool, 0, [
|
||||
[{ json: { response: [{ result: 'test' }] } }],
|
||||
]);
|
||||
expect(result).toBe(JSON.stringify([{ result: 'test' }]));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user