diff --git a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/README.md b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/README.md index 5d0f4023ebb..7ad235182f1 100644 --- a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/README.md +++ b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/README.md @@ -125,6 +125,7 @@ interface VectorStoreNodeConstructorArgs { populateVectorStore: Function; // Store documents in vector store (accepts batches in v1.1+) getVectorStoreClient: Function; // Get vector store instance releaseVectorStoreClient?: Function; // Clean up resources + searchByText?: true; // Text-search modes call similaritySearchWithScore and skip embedQuery } ``` diff --git a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/loadOperation.test.ts b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/loadOperation.test.ts index 19e99e021bf..99455c2df34 100644 --- a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/loadOperation.test.ts +++ b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/loadOperation.test.ts @@ -109,6 +109,32 @@ describe('handleLoadOperation', () => { expect(result).toHaveLength(3); }); + it('should search by text when searchByText is set', async () => { + mockVectorStore.similaritySearchWithScore.mockResolvedValue([ + [{ pageContent: 'test content 1', metadata: { test: 'metadata 1' } } as Document, 0.95], + [{ pageContent: 'test content 2', metadata: { test: 'metadata 2' } } as Document, 0.85], + [{ pageContent: 'test content 3', metadata: { test: 'metadata 3' } } as Document, 0.75], + ]); + + const result = await handleLoadOperation( + mockContext, + { ...mockArgs, searchByText: true }, + mockEmbeddings, + 0, + ); + + expect(mockVectorStore.similaritySearchWithScore).toHaveBeenCalledWith('test search query', 3, { + testFilter: 'value', + }); + expect(mockEmbeddings.embedQuery).not.toHaveBeenCalled(); + expect(mockVectorStore.similaritySearchVectorWithScore).not.toHaveBeenCalled(); + expect(result.map((item) => (item.json.document as IDataObject).pageContent)).toEqual([ + 'test content 1', + 'test content 2', + 'test content 3', + ]); + }); + it('should include document metadata when includeDocumentMetadata is true', async () => { const result = await handleLoadOperation(mockContext, mockArgs, mockEmbeddings, 0); diff --git a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/retrieveAsToolExecuteOperation.test.ts b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/retrieveAsToolExecuteOperation.test.ts index 5d7d24c5a1d..fffa7ddfbda 100644 --- a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/retrieveAsToolExecuteOperation.test.ts +++ b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/retrieveAsToolExecuteOperation.test.ts @@ -141,6 +141,33 @@ describe('handleRetrieveAsToolExecuteOperation', () => { }); }); + it('should search by text when searchByText is set', async () => { + mockVectorStore.similaritySearchWithScore.mockResolvedValue([ + [{ pageContent: 'test content 1', metadata: { test: 'metadata 1' } } as Document, 0.95], + [{ pageContent: 'test content 2', metadata: { test: 'metadata 2' } } as Document, 0.85], + [{ pageContent: 'test content 3', metadata: { test: 'metadata 3' } } as Document, 0.75], + ]); + + const result = await handleRetrieveAsToolExecuteOperation( + mockContext, + { ...mockArgs, searchByText: true }, + mockEmbeddings, + 0, + ); + + expect(mockVectorStore.similaritySearchWithScore).toHaveBeenCalledWith('test search query', 3, { + testFilter: 'value', + }); + expect(mockEmbeddings.embedQuery).not.toHaveBeenCalled(); + expect(mockVectorStore.similaritySearchVectorWithScore).not.toHaveBeenCalled(); + const response = result[0].json.response as Array<{ type: string; text: string }>; + expect(response.map((entry) => JSON.parse(entry.text).pageContent)).toEqual([ + 'test content 1', + 'test content 2', + 'test content 3', + ]); + }); + it('should throw error when input data does not contain query', async () => { inputData[0].json = { notQuery: 'some value' }; diff --git a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/retrieveAsToolOperation.test.ts b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/retrieveAsToolOperation.test.ts index 19a3ca35121..88baacc7db3 100644 --- a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/retrieveAsToolOperation.test.ts +++ b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/__tests__/retrieveAsToolOperation.test.ts @@ -180,6 +180,34 @@ describe('handleRetrieveAsToolOperation', () => { expect(mockArgs.releaseVectorStoreClient).toHaveBeenCalledWith(mockVectorStore); }); + it('should search by text when searchByText is set', async () => { + mockVectorStore.similaritySearchWithScore.mockResolvedValue([ + [{ pageContent: 'test content 1', metadata: { test: 'metadata 1' } } as Document, 0.95], + [{ pageContent: 'test content 2', metadata: { test: 'metadata 2' } } as Document, 0.85], + [{ pageContent: 'test content 3', metadata: { test: 'metadata 3' } } as Document, 0.75], + ]); + const result = await handleRetrieveAsToolOperation( + mockContext, + { ...mockArgs, searchByText: true }, + mockEmbeddings, + 0, + ); + const tool = result.response as DynamicTool; + + const toolResult = await tool.invoke({ input: 'test query' }); + + expect(mockVectorStore.similaritySearchWithScore).toHaveBeenCalledWith('test query', 3, { + testFilter: 'value', + }); + expect(mockEmbeddings.embedQuery).not.toHaveBeenCalled(); + expect(mockVectorStore.similaritySearchVectorWithScore).not.toHaveBeenCalled(); + expect(toolResult).toHaveLength(3); + expect(JSON.parse(toolResult[0].text)).toEqual({ + pageContent: 'test content 1', + metadata: { test: 'metadata 1' }, + }); + }); + it('should include metadata in results when includeDocumentMetadata is true', async () => { const result = await handleRetrieveAsToolOperation(mockContext, mockArgs, mockEmbeddings, 0); const tool = result.response as DynamicTool; diff --git a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/loadOperation.ts b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/loadOperation.ts index afd22de87d1..7e78d9d9e3f 100644 --- a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/loadOperation.ts +++ b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/loadOperation.ts @@ -39,11 +39,15 @@ export async function handleLoadOperation( true, ) as boolean; - // Embed the prompt to prepare for vector similarity search - const embeddedPrompt = await embeddings.embedQuery(prompt); - - // Get the most similar documents to the embedded prompt - let docs = await vectorStore.similaritySearchVectorWithScore(embeddedPrompt, topK, filter); + // Get the most similar documents to the prompt + let docs = + args.searchByText === true + ? await vectorStore.similaritySearchWithScore(prompt, topK, filter) + : await vectorStore.similaritySearchVectorWithScore( + await embeddings.embedQuery(prompt), + topK, + filter, + ); // If reranker is used, rerank the documents if (useReranker && docs.length > 0) { diff --git a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/retrieveAsToolExecuteOperation.ts b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/retrieveAsToolExecuteOperation.ts index d5736611baa..bb8547e3df8 100644 --- a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/retrieveAsToolExecuteOperation.ts +++ b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/retrieveAsToolExecuteOperation.ts @@ -50,11 +50,15 @@ export async function handleRetrieveAsToolExecuteOperation 0) { diff --git a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/retrieveAsToolOperation.ts b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/retrieveAsToolOperation.ts index 11bdb9bff3e..ec3c854ff6f 100644 --- a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/retrieveAsToolOperation.ts +++ b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/operations/retrieveAsToolOperation.ts @@ -61,15 +61,15 @@ export async function handleRetrieveAsToolOperation 0) { diff --git a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/types.ts b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/types.ts index 80d822c50ce..873eaf912cb 100644 --- a/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/types.ts +++ b/packages/@n8n/ai-utilities/src/utils/vector-store/createVectorStoreNode/types.ts @@ -2,17 +2,13 @@ import type { Document } from '@langchain/core/documents'; import type { Embeddings } from '@langchain/core/embeddings'; import type { VectorStore } from '@langchain/core/vectorstores'; import type { - ICredentialTestFunction, IExecuteFunctions, INodeCredentialDescription, INodeProperties, - ILoadOptionsFunctions, - INodeListSearchResult, + INodeType, Icon, ISupplyDataFunctions, ThemeIconColor, - IDataObject, - NodeParameterValueType, IBuilderHint, } from 'n8n-workflow'; @@ -35,24 +31,7 @@ export interface NodeMeta { export interface VectorStoreNodeConstructorArgs { meta: NodeMeta; - methods?: { - listSearch?: { - [key: string]: ( - this: ILoadOptionsFunctions, - filter?: string, - paginationToken?: string, - ) => Promise; - }; - actionHandler?: { - [functionName: string]: ( - this: ILoadOptionsFunctions, - payload: IDataObject | string | undefined, - ) => Promise; - }; - credentialTest?: { - [functionName: string]: ICredentialTestFunction; - }; - }; + methods?: INodeType['methods']; sharedFields: INodeProperties[]; insertFields?: INodeProperties[]; @@ -60,6 +39,13 @@ export interface VectorStoreNodeConstructorArgs