mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +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",
|
||||
"displayOptions": {
|
||||
"show": {
|
||||
"@version": [
|
||||
{
|
||||
"_cnd": {
|
||||
"lte": 1.2,
|
||||
},
|
||||
},
|
||||
],
|
||||
"mode": [
|
||||
"retrieve-as-tool",
|
||||
],
|
||||
@@ -254,6 +261,7 @@ exports[`createVectorStoreNode retrieve mode supplies vector store as data 1`] =
|
||||
1,
|
||||
1.1,
|
||||
1.2,
|
||||
1.3,
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -95,15 +95,23 @@ describe('createVectorStoreNode', () => {
|
||||
});
|
||||
|
||||
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
|
||||
const parameters: Record<string, NodeParameterValueType | object> = {
|
||||
const parameters: Record<string, NodeParameterValueType> = {
|
||||
...DEFAULT_PARAMETERS,
|
||||
mode: 'retrieve-as-tool',
|
||||
description: 'tool description',
|
||||
toolName: 'tool name',
|
||||
includeDocumentMetadata: true,
|
||||
};
|
||||
context.getNode.mockReturnValueOnce({
|
||||
id: 'testNode',
|
||||
typeVersion: 1.2,
|
||||
name: 'Test Tool',
|
||||
type: 'testVectorStore',
|
||||
parameters,
|
||||
position: [0, 0],
|
||||
});
|
||||
context.getNodeParameter.mockImplementation(
|
||||
(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
|
||||
const parameters: Record<string, NodeParameterValueType | object> = {
|
||||
const parameters: Record<string, NodeParameterValueType> = {
|
||||
...DEFAULT_PARAMETERS,
|
||||
mode: 'retrieve-as-tool',
|
||||
description: 'tool description',
|
||||
toolName: 'tool name',
|
||||
includeDocumentMetadata: false,
|
||||
includeDocumentMetadata: true,
|
||||
};
|
||||
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],
|
||||
);
|
||||
@@ -151,7 +166,49 @@ describe('createVectorStoreNode', () => {
|
||||
const output = await tool?.func(MOCK_SEARCH_VALUE);
|
||||
|
||||
// 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(embeddings.embedQuery).toHaveBeenCalledWith(MOCK_SEARCH_VALUE);
|
||||
expect(vectorStore.similaritySearchVectorWithScore).toHaveBeenCalledWith(
|
||||
|
||||
@@ -44,7 +44,8 @@ export const createVectorStoreNode = <T extends VectorStore = VectorStore>(
|
||||
iconColor: args.meta.iconColor,
|
||||
group: ['transform'],
|
||||
// 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: {
|
||||
name: args.meta.displayName,
|
||||
},
|
||||
@@ -125,6 +126,7 @@ export const createVectorStoreNode = <T extends VectorStore = VectorStore>(
|
||||
validateType: 'string-alphanumeric',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'@version': [{ _cnd: { lte: 1.2 } }],
|
||||
mode: ['retrieve-as-tool'],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -30,6 +30,14 @@ describe('Vector Store Operation Handlers', () => {
|
||||
};
|
||||
|
||||
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) => {
|
||||
if (typeof parameterName !== 'string') return fallbackValue;
|
||||
return nodeParameters[parameterName] ?? fallbackValue;
|
||||
@@ -103,12 +111,29 @@ describe('Vector Store Operation Handlers', () => {
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
expect(result).toHaveProperty('response');
|
||||
expect(result.response).toHaveProperty('name', 'test_tool');
|
||||
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
|
||||
jest.mock('@utils/helpers', () => ({
|
||||
...jest.requireActual('@utils/helpers'),
|
||||
getMetadataFiltersValues: jest.fn().mockReturnValue({ testFilter: 'value' }),
|
||||
}));
|
||||
|
||||
@@ -37,6 +38,14 @@ describe('handleRetrieveAsToolOperation', () => {
|
||||
};
|
||||
|
||||
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) => {
|
||||
if (typeof parameterName !== 'string') return fallbackValue;
|
||||
return nodeParameters[parameterName] ?? fallbackValue;
|
||||
@@ -70,7 +79,16 @@ describe('handleRetrieveAsToolOperation', () => {
|
||||
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(
|
||||
mockContext,
|
||||
mockArgs,
|
||||
@@ -89,6 +107,25 @@ describe('handleRetrieveAsToolOperation', () => {
|
||||
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 () => {
|
||||
const result = await handleRetrieveAsToolOperation(mockContext, mockArgs, mockEmbeddings, 0);
|
||||
const tool = result.response as DynamicTool;
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { VectorStore } from '@langchain/core/vectorstores';
|
||||
import { DynamicTool } from 'langchain/tools';
|
||||
import type { ISupplyDataFunctions, SupplyData } from 'n8n-workflow';
|
||||
|
||||
import { getMetadataFiltersValues } from '@utils/helpers';
|
||||
import { getMetadataFiltersValues, nodeNameToToolName } from '@utils/helpers';
|
||||
import { logWrapper } from '@utils/logWrapper';
|
||||
|
||||
import type { VectorStoreNodeConstructorArgs } from '../types';
|
||||
@@ -20,7 +20,14 @@ export async function handleRetrieveAsToolOperation<T extends VectorStore = Vect
|
||||
): Promise<SupplyData> {
|
||||
// Get the tool configuration parameters
|
||||
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 includeDocumentMetadata = context.getNodeParameter(
|
||||
'includeDocumentMetadata',
|
||||
|
||||
Reference in New Issue
Block a user