mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
feat(editor): Use node name as tool name at Vector Store retriever tool nodes (#15917)
This commit is contained in:
@@ -127,6 +127,13 @@ exports[`createVectorStoreNode retrieve mode supplies vector store as data 1`] =
|
|||||||
"displayName": "Name",
|
"displayName": "Name",
|
||||||
"displayOptions": {
|
"displayOptions": {
|
||||||
"show": {
|
"show": {
|
||||||
|
"@version": [
|
||||||
|
{
|
||||||
|
"_cnd": {
|
||||||
|
"lte": 1.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
"mode": [
|
"mode": [
|
||||||
"retrieve-as-tool",
|
"retrieve-as-tool",
|
||||||
],
|
],
|
||||||
@@ -254,6 +261,7 @@ exports[`createVectorStoreNode retrieve mode supplies vector store as data 1`] =
|
|||||||
1,
|
1,
|
||||||
1.1,
|
1.1,
|
||||||
1.2,
|
1.2,
|
||||||
|
1.3,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -95,15 +95,23 @@ describe('createVectorStoreNode', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('retrieve-as-tool mode', () => {
|
describe('retrieve-as-tool mode', () => {
|
||||||
it('supplies DynamicTool that queries vector store and returns documents with metadata', async () => {
|
it('supplies DynamicTool that queries vector store and returns documents with metadata on version <= 1.2', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const parameters: Record<string, NodeParameterValueType | object> = {
|
const parameters: Record<string, NodeParameterValueType> = {
|
||||||
...DEFAULT_PARAMETERS,
|
...DEFAULT_PARAMETERS,
|
||||||
mode: 'retrieve-as-tool',
|
mode: 'retrieve-as-tool',
|
||||||
description: 'tool description',
|
description: 'tool description',
|
||||||
toolName: 'tool name',
|
toolName: 'tool name',
|
||||||
includeDocumentMetadata: true,
|
includeDocumentMetadata: true,
|
||||||
};
|
};
|
||||||
|
context.getNode.mockReturnValueOnce({
|
||||||
|
id: 'testNode',
|
||||||
|
typeVersion: 1.2,
|
||||||
|
name: 'Test Tool',
|
||||||
|
type: 'testVectorStore',
|
||||||
|
parameters,
|
||||||
|
position: [0, 0],
|
||||||
|
});
|
||||||
context.getNodeParameter.mockImplementation(
|
context.getNodeParameter.mockImplementation(
|
||||||
(parameterName: string): NodeParameterValueType | object => parameters[parameterName],
|
(parameterName: string): NodeParameterValueType | object => parameters[parameterName],
|
||||||
);
|
);
|
||||||
@@ -130,15 +138,22 @@ describe('createVectorStoreNode', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supplies DynamicTool that queries vector store and returns documents without metadata', async () => {
|
it('supplies DynamicTool that queries vector store and returns documents with metadata on version > 1.2', async () => {
|
||||||
// ARRANGE
|
// ARRANGE
|
||||||
const parameters: Record<string, NodeParameterValueType | object> = {
|
const parameters: Record<string, NodeParameterValueType> = {
|
||||||
...DEFAULT_PARAMETERS,
|
...DEFAULT_PARAMETERS,
|
||||||
mode: 'retrieve-as-tool',
|
mode: 'retrieve-as-tool',
|
||||||
description: 'tool description',
|
description: 'tool description',
|
||||||
toolName: 'tool name',
|
includeDocumentMetadata: true,
|
||||||
includeDocumentMetadata: false,
|
|
||||||
};
|
};
|
||||||
|
context.getNode.mockReturnValueOnce({
|
||||||
|
id: 'testNode',
|
||||||
|
typeVersion: 1.3,
|
||||||
|
name: 'Test Tool',
|
||||||
|
type: 'testVectorStore',
|
||||||
|
parameters,
|
||||||
|
position: [0, 0],
|
||||||
|
});
|
||||||
context.getNodeParameter.mockImplementation(
|
context.getNodeParameter.mockImplementation(
|
||||||
(parameterName: string): NodeParameterValueType | object => parameters[parameterName],
|
(parameterName: string): NodeParameterValueType | object => parameters[parameterName],
|
||||||
);
|
);
|
||||||
@@ -151,7 +166,49 @@ describe('createVectorStoreNode', () => {
|
|||||||
const output = await tool?.func(MOCK_SEARCH_VALUE);
|
const output = await tool?.func(MOCK_SEARCH_VALUE);
|
||||||
|
|
||||||
// ASSERT
|
// ASSERT
|
||||||
expect(tool?.getName()).toEqual(parameters.toolName);
|
expect(tool?.getName()).toEqual('Test_Tool');
|
||||||
|
expect(tool?.description).toEqual(parameters.toolDescription);
|
||||||
|
expect(embeddings.embedQuery).toHaveBeenCalledWith(MOCK_SEARCH_VALUE);
|
||||||
|
expect(vectorStore.similaritySearchVectorWithScore).toHaveBeenCalledWith(
|
||||||
|
MOCK_EMBEDDED_SEARCH_VALUE,
|
||||||
|
parameters.topK,
|
||||||
|
parameters.filter,
|
||||||
|
);
|
||||||
|
expect(output).toEqual([
|
||||||
|
{ type: 'text', text: JSON.stringify(MOCK_DOCUMENTS[0][0]) },
|
||||||
|
{ type: 'text', text: JSON.stringify(MOCK_DOCUMENTS[1][0]) },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supplies DynamicTool that queries vector store and returns documents without metadata', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const parameters: Record<string, NodeParameterValueType> = {
|
||||||
|
...DEFAULT_PARAMETERS,
|
||||||
|
mode: 'retrieve-as-tool',
|
||||||
|
description: 'tool description',
|
||||||
|
includeDocumentMetadata: false,
|
||||||
|
};
|
||||||
|
context.getNode.mockReturnValueOnce({
|
||||||
|
id: 'testNode',
|
||||||
|
typeVersion: 1.3,
|
||||||
|
name: 'Test Tool',
|
||||||
|
type: 'testVectorStore',
|
||||||
|
parameters,
|
||||||
|
position: [0, 0],
|
||||||
|
});
|
||||||
|
context.getNodeParameter.mockImplementation(
|
||||||
|
(parameterName: string): NodeParameterValueType | object => parameters[parameterName],
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
const VectorStoreNodeType = createVectorStoreNode(vectorStoreNodeArgs);
|
||||||
|
const nodeType = new VectorStoreNodeType();
|
||||||
|
const data = await nodeType.supplyData.call(context, 1);
|
||||||
|
const tool = (data.response as { logWrapped: DynamicTool }).logWrapped;
|
||||||
|
const output = await tool?.func(MOCK_SEARCH_VALUE);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tool?.getName()).toEqual('Test_Tool');
|
||||||
expect(tool?.description).toEqual(parameters.toolDescription);
|
expect(tool?.description).toEqual(parameters.toolDescription);
|
||||||
expect(embeddings.embedQuery).toHaveBeenCalledWith(MOCK_SEARCH_VALUE);
|
expect(embeddings.embedQuery).toHaveBeenCalledWith(MOCK_SEARCH_VALUE);
|
||||||
expect(vectorStore.similaritySearchVectorWithScore).toHaveBeenCalledWith(
|
expect(vectorStore.similaritySearchVectorWithScore).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export const createVectorStoreNode = <T extends VectorStore = VectorStore>(
|
|||||||
iconColor: args.meta.iconColor,
|
iconColor: args.meta.iconColor,
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
// 1.2 has changes to VectorStoreInMemory node.
|
// 1.2 has changes to VectorStoreInMemory node.
|
||||||
version: [1, 1.1, 1.2],
|
// 1.3 drops `toolName` and uses node name as the tool name.
|
||||||
|
version: [1, 1.1, 1.2, 1.3],
|
||||||
defaults: {
|
defaults: {
|
||||||
name: args.meta.displayName,
|
name: args.meta.displayName,
|
||||||
},
|
},
|
||||||
@@ -125,6 +126,7 @@ export const createVectorStoreNode = <T extends VectorStore = VectorStore>(
|
|||||||
validateType: 'string-alphanumeric',
|
validateType: 'string-alphanumeric',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
|
'@version': [{ _cnd: { lte: 1.2 } }],
|
||||||
mode: ['retrieve-as-tool'],
|
mode: ['retrieve-as-tool'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ describe('Vector Store Operation Handlers', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockContext = mock<IExecuteFunctions & ISupplyDataFunctions>();
|
mockContext = mock<IExecuteFunctions & ISupplyDataFunctions>();
|
||||||
|
mockContext.getNode.mockReturnValue({
|
||||||
|
id: 'testNode',
|
||||||
|
typeVersion: 1.3,
|
||||||
|
name: 'Test Tool',
|
||||||
|
type: 'testVectorStore',
|
||||||
|
parameters: nodeParameters,
|
||||||
|
position: [0, 0],
|
||||||
|
});
|
||||||
mockContext.getNodeParameter.mockImplementation((parameterName, _itemIndex, fallbackValue) => {
|
mockContext.getNodeParameter.mockImplementation((parameterName, _itemIndex, fallbackValue) => {
|
||||||
if (typeof parameterName !== 'string') return fallbackValue;
|
if (typeof parameterName !== 'string') return fallbackValue;
|
||||||
return nodeParameters[parameterName] ?? fallbackValue;
|
return nodeParameters[parameterName] ?? fallbackValue;
|
||||||
@@ -103,12 +111,29 @@ describe('Vector Store Operation Handlers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('handleRetrieveAsToolOperation', () => {
|
describe('handleRetrieveAsToolOperation', () => {
|
||||||
it('should return a tool with the correct name and description', async () => {
|
it('should return a tool with the correct name and description on version <= 1.2', async () => {
|
||||||
|
mockContext.getNode.mockReturnValueOnce({
|
||||||
|
id: 'testNode',
|
||||||
|
typeVersion: 1.2,
|
||||||
|
name: 'Test Tool',
|
||||||
|
type: 'testVectorStore',
|
||||||
|
parameters: nodeParameters,
|
||||||
|
position: [0, 0],
|
||||||
|
});
|
||||||
|
|
||||||
const result = await handleRetrieveAsToolOperation(mockContext, mockArgs, mockEmbeddings, 0);
|
const result = await handleRetrieveAsToolOperation(mockContext, mockArgs, mockEmbeddings, 0);
|
||||||
|
|
||||||
expect(result).toHaveProperty('response');
|
expect(result).toHaveProperty('response');
|
||||||
expect(result.response).toHaveProperty('name', 'test_tool');
|
expect(result.response).toHaveProperty('name', 'test_tool');
|
||||||
expect(result.response).toHaveProperty('description', 'Test tool description');
|
expect(result.response).toHaveProperty('description', 'Test tool description');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return a tool with the correct name and description on version > 1.2', async () => {
|
||||||
|
const result = await handleRetrieveAsToolOperation(mockContext, mockArgs, mockEmbeddings, 0);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('response');
|
||||||
|
expect(result.response).toHaveProperty('name', 'Test_Tool');
|
||||||
|
expect(result.response).toHaveProperty('description', 'Test tool description');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { handleRetrieveAsToolOperation } from '../retrieveAsToolOperation';
|
|||||||
|
|
||||||
// Mock the helper functions
|
// Mock the helper functions
|
||||||
jest.mock('@utils/helpers', () => ({
|
jest.mock('@utils/helpers', () => ({
|
||||||
|
...jest.requireActual('@utils/helpers'),
|
||||||
getMetadataFiltersValues: jest.fn().mockReturnValue({ testFilter: 'value' }),
|
getMetadataFiltersValues: jest.fn().mockReturnValue({ testFilter: 'value' }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -37,6 +38,14 @@ describe('handleRetrieveAsToolOperation', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockContext = mock<ISupplyDataFunctions>();
|
mockContext = mock<ISupplyDataFunctions>();
|
||||||
|
mockContext.getNode.mockReturnValue({
|
||||||
|
id: 'testNode',
|
||||||
|
typeVersion: 1.3,
|
||||||
|
name: 'Test Knowledge Base',
|
||||||
|
type: 'testVectorStore',
|
||||||
|
parameters: nodeParameters,
|
||||||
|
position: [0, 0],
|
||||||
|
});
|
||||||
mockContext.getNodeParameter.mockImplementation((parameterName, _itemIndex, fallbackValue) => {
|
mockContext.getNodeParameter.mockImplementation((parameterName, _itemIndex, fallbackValue) => {
|
||||||
if (typeof parameterName !== 'string') return fallbackValue;
|
if (typeof parameterName !== 'string') return fallbackValue;
|
||||||
return nodeParameters[parameterName] ?? fallbackValue;
|
return nodeParameters[parameterName] ?? fallbackValue;
|
||||||
@@ -70,7 +79,16 @@ describe('handleRetrieveAsToolOperation', () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a dynamic tool with the correct name and description', async () => {
|
it('should create a dynamic tool with the correct name and description on version <= 1.2', async () => {
|
||||||
|
mockContext.getNode.mockReturnValueOnce({
|
||||||
|
id: 'testNode',
|
||||||
|
typeVersion: 1.2,
|
||||||
|
name: 'Test Knowledge Base',
|
||||||
|
type: 'testVectorStore',
|
||||||
|
parameters: nodeParameters,
|
||||||
|
position: [0, 0],
|
||||||
|
});
|
||||||
|
|
||||||
const result = (await handleRetrieveAsToolOperation(
|
const result = (await handleRetrieveAsToolOperation(
|
||||||
mockContext,
|
mockContext,
|
||||||
mockArgs,
|
mockArgs,
|
||||||
@@ -89,6 +107,25 @@ describe('handleRetrieveAsToolOperation', () => {
|
|||||||
expect(logWrapper).toHaveBeenCalledWith(expect.any(DynamicTool), mockContext);
|
expect(logWrapper).toHaveBeenCalledWith(expect.any(DynamicTool), mockContext);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create a dynamic tool with the correct name and description on version > 1.2', async () => {
|
||||||
|
const result = (await handleRetrieveAsToolOperation(
|
||||||
|
mockContext,
|
||||||
|
mockArgs,
|
||||||
|
mockEmbeddings,
|
||||||
|
0,
|
||||||
|
)) as {
|
||||||
|
response: DynamicTool;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('response');
|
||||||
|
expect(result.response).toBeInstanceOf(DynamicTool);
|
||||||
|
expect(result.response.name).toBe('Test_Knowledge_Base');
|
||||||
|
expect(result.response.description).toBe('Search the test knowledge base');
|
||||||
|
|
||||||
|
// Check logWrapper was called
|
||||||
|
expect(logWrapper).toHaveBeenCalledWith(expect.any(DynamicTool), mockContext);
|
||||||
|
});
|
||||||
|
|
||||||
it('should create a tool that can search the vector store', async () => {
|
it('should create a tool that can search the vector store', async () => {
|
||||||
const result = await handleRetrieveAsToolOperation(mockContext, mockArgs, mockEmbeddings, 0);
|
const result = await handleRetrieveAsToolOperation(mockContext, mockArgs, mockEmbeddings, 0);
|
||||||
const tool = result.response as DynamicTool;
|
const tool = result.response as DynamicTool;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { VectorStore } from '@langchain/core/vectorstores';
|
|||||||
import { DynamicTool } from 'langchain/tools';
|
import { DynamicTool } from 'langchain/tools';
|
||||||
import type { ISupplyDataFunctions, SupplyData } from 'n8n-workflow';
|
import type { ISupplyDataFunctions, SupplyData } from 'n8n-workflow';
|
||||||
|
|
||||||
import { getMetadataFiltersValues } from '@utils/helpers';
|
import { getMetadataFiltersValues, nodeNameToToolName } from '@utils/helpers';
|
||||||
import { logWrapper } from '@utils/logWrapper';
|
import { logWrapper } from '@utils/logWrapper';
|
||||||
|
|
||||||
import type { VectorStoreNodeConstructorArgs } from '../types';
|
import type { VectorStoreNodeConstructorArgs } from '../types';
|
||||||
@@ -20,7 +20,14 @@ export async function handleRetrieveAsToolOperation<T extends VectorStore = Vect
|
|||||||
): Promise<SupplyData> {
|
): Promise<SupplyData> {
|
||||||
// Get the tool configuration parameters
|
// Get the tool configuration parameters
|
||||||
const toolDescription = context.getNodeParameter('toolDescription', itemIndex) as string;
|
const toolDescription = context.getNodeParameter('toolDescription', itemIndex) as string;
|
||||||
const toolName = context.getNodeParameter('toolName', itemIndex) as string;
|
|
||||||
|
const node = context.getNode();
|
||||||
|
const { typeVersion } = node;
|
||||||
|
const toolName =
|
||||||
|
typeVersion < 1.3
|
||||||
|
? (context.getNodeParameter('toolName', itemIndex) as string)
|
||||||
|
: nodeNameToToolName(node);
|
||||||
|
|
||||||
const topK = context.getNodeParameter('topK', itemIndex, 4) as number;
|
const topK = context.getNodeParameter('topK', itemIndex, 4) as number;
|
||||||
const includeDocumentMetadata = context.getNodeParameter(
|
const includeDocumentMetadata = context.getNodeParameter(
|
||||||
'includeDocumentMetadata',
|
'includeDocumentMetadata',
|
||||||
|
|||||||
Reference in New Issue
Block a user