mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat: Support vector stores as tools
This commit is contained in:
parent
956b11a560
commit
819a397735
|
@ -25,6 +25,7 @@ import { logWrapper } from '../../../utils/logWrapper';
|
||||||
import type { N8nBinaryLoader } from '../../../utils/N8nBinaryLoader';
|
import type { N8nBinaryLoader } from '../../../utils/N8nBinaryLoader';
|
||||||
import { N8nJsonLoader } from '../../../utils/N8nJsonLoader';
|
import { N8nJsonLoader } from '../../../utils/N8nJsonLoader';
|
||||||
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
|
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
|
||||||
|
import { DynamicTool } from 'langchain/tools';
|
||||||
|
|
||||||
type NodeOperationMode = 'insert' | 'load' | 'retrieve' | 'update';
|
type NodeOperationMode = 'insert' | 'load' | 'retrieve' | 'update';
|
||||||
|
|
||||||
|
@ -124,11 +125,12 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
name: args.meta.name,
|
name: args.meta.name,
|
||||||
description: args.meta.description,
|
description: args.meta.description,
|
||||||
icon: args.meta.icon,
|
icon: args.meta.icon,
|
||||||
group: ['transform'],
|
group: ['transform', 'vector-store'],
|
||||||
version: 1,
|
version: 1,
|
||||||
defaults: {
|
defaults: {
|
||||||
name: args.meta.displayName,
|
name: args.meta.displayName,
|
||||||
},
|
},
|
||||||
|
usableAsTool: true,
|
||||||
codex: {
|
codex: {
|
||||||
categories: ['AI'],
|
categories: ['AI'],
|
||||||
subcategories: {
|
subcategories: {
|
||||||
|
@ -210,7 +212,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
description: 'Number of top results to fetch from vector store',
|
description: 'Number of top results to fetch from vector store',
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
mode: ['load'],
|
mode: ['load', 'retrieve'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -377,7 +379,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
|
|
||||||
throw new NodeOperationError(
|
throw new NodeOperationError(
|
||||||
this.getNode(),
|
this.getNode(),
|
||||||
'Only the "load" and "insert" operation modes are supported with execute',
|
'Only the "load", "update" and "insert" operation modes are supported with execute',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,16 +391,59 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
|
||||||
0,
|
0,
|
||||||
)) as Embeddings;
|
)) as Embeddings;
|
||||||
|
|
||||||
if (mode === 'retrieve') {
|
if (mode !== 'retrieve') {
|
||||||
const vectorStore = await args.getVectorStoreClient(this, filter, embeddings, itemIndex);
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
'Only the "retrieve" operation mode is supported to supply data',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Tool" is appended automatically to node type name
|
||||||
|
// because of usableAsTool flag set above
|
||||||
|
const isTool = this.getNode().type.endsWith('Tool');
|
||||||
|
if (isTool) {
|
||||||
|
const toolDescription = this.getNodeParameter('toolDescription', itemIndex) as string;
|
||||||
|
const toolName = this.getNodeParameter('toolName', itemIndex) as string;
|
||||||
|
const topK = this.getNodeParameter('topK', itemIndex, 4) as number;
|
||||||
|
|
||||||
|
const vectorStoreTool = new DynamicTool({
|
||||||
|
name: toolName,
|
||||||
|
description: toolDescription,
|
||||||
|
func: async (input) => {
|
||||||
|
const vectorStore = await args.getVectorStoreClient(
|
||||||
|
this,
|
||||||
|
filter,
|
||||||
|
embeddings,
|
||||||
|
itemIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
const embeddedPrompt = await embeddings.embedQuery(input);
|
||||||
|
const documents = await vectorStore.similaritySearchVectorWithScore(
|
||||||
|
embeddedPrompt,
|
||||||
|
topK,
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
|
||||||
|
return documents
|
||||||
|
.map((document) => {
|
||||||
|
// Tools can only return a string or array of objects with type text
|
||||||
|
// todo return concatenated strings instead?
|
||||||
|
return { type: 'text', text: document[0].pageContent };
|
||||||
|
// todo with metadata?
|
||||||
|
// return { type: 'text', text: JSON.stringify(document[0]) };
|
||||||
|
})
|
||||||
|
.filter((document) => !!document);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
response: logWrapper(vectorStore, this),
|
response: logWrapper(vectorStoreTool, this),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NodeOperationError(
|
const vectorStore = await args.getVectorStoreClient(this, filter, embeddings, itemIndex);
|
||||||
this.getNode(),
|
return {
|
||||||
'Only the "retrieve" operation mode is supported to supply data',
|
response: logWrapper(vectorStore, this),
|
||||||
);
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,7 +46,9 @@ export class NodeTypes implements INodeTypes {
|
||||||
|
|
||||||
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||||
const origType = nodeType;
|
const origType = nodeType;
|
||||||
const toolRequested = nodeType.startsWith('n8n-nodes-base') && nodeType.endsWith('Tool');
|
const toolRequested =
|
||||||
|
(nodeType.startsWith('n8n-nodes-base') || nodeType.startsWith('@n8n/n8n-nodes-langchain')) &&
|
||||||
|
nodeType.endsWith('Tool');
|
||||||
// Make sure the nodeType to actually get from disk is the un-wrapped type
|
// Make sure the nodeType to actually get from disk is the un-wrapped type
|
||||||
if (toolRequested) {
|
if (toolRequested) {
|
||||||
nodeType = nodeType.replace(/Tool$/, '');
|
nodeType = nodeType.replace(/Tool$/, '');
|
||||||
|
|
|
@ -365,8 +365,13 @@ export function convertNodeToAiTool<
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFullDescription(item.description)) {
|
if (isFullDescription(item.description)) {
|
||||||
|
const isVectorStore = item.description.group.includes('vector-store');
|
||||||
|
|
||||||
item.description.name += 'Tool';
|
item.description.name += 'Tool';
|
||||||
item.description.inputs = [];
|
if (!isVectorStore) {
|
||||||
|
item.description.inputs = [];
|
||||||
|
}
|
||||||
|
|
||||||
item.description.outputs = [NodeConnectionType.AiTool];
|
item.description.outputs = [NodeConnectionType.AiTool];
|
||||||
item.description.displayName += ' Tool';
|
item.description.displayName += ' Tool';
|
||||||
delete item.description.usableAsTool;
|
delete item.description.usableAsTool;
|
||||||
|
@ -417,6 +422,21 @@ export function convertNodeToAiTool<
|
||||||
|
|
||||||
item.description.properties.unshift(descProp);
|
item.description.properties.unshift(descProp);
|
||||||
|
|
||||||
|
if (isVectorStore) {
|
||||||
|
const nameProp: INodeProperties = {
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'toolName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'Name of the vector store',
|
||||||
|
placeholder: 'e.g. company_knowledge_base',
|
||||||
|
validateType: 'string-alphanumeric',
|
||||||
|
};
|
||||||
|
|
||||||
|
item.description.properties.unshift(nameProp);
|
||||||
|
}
|
||||||
|
|
||||||
// If node has resource or operation we can determine pre-populate tool description based on it
|
// If node has resource or operation we can determine pre-populate tool description based on it
|
||||||
// so we add the descriptionType property as the first property
|
// so we add the descriptionType property as the first property
|
||||||
if (hasResource || hasOperation) {
|
if (hasResource || hasOperation) {
|
||||||
|
|
Loading…
Reference in a new issue