From 28f87d7ecbe5b0d91f3ddc8a0f04f36174622ad6 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Thu, 5 Dec 2024 17:23:42 +0100 Subject: [PATCH] spike/poc work --- .../agents/Agent/agents/ToolsAgent/execute.ts | 1 + .../shared/createVectorStoreNode.ts | 62 +++++++++++++++---- packages/cli/src/node-types.ts | 1 + packages/core/src/NodeExecuteFunctions.ts | 3 + packages/workflow/src/NodeHelpers.ts | 4 +- 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/execute.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/execute.ts index 74d6819961..20403453e9 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/execute.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/execute.ts @@ -270,6 +270,7 @@ export async function toolsAgentExecute(this: IExecuteFunctions): Promise defaults: { name: args.meta.displayName, }, + usableAsTool: true, codex: { categories: ['AI'], subcategories: { @@ -143,11 +147,13 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) => }, }, credentials: args.meta.credentials, + // todo add model input only if used as tool + // todo use model from parent by default only show model input if custom mode is used // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node inputs: `={{ ((parameters) => { const mode = parameters?.mode; - const inputs = [{ displayName: "Embedding", type: "${NodeConnectionType.AiEmbedding}", required: true, maxConnections: 1}] + const inputs = [{ displayName: "Embedding", type: "${NodeConnectionType.AiEmbedding}", required: true, maxConnections: 1}, {displayName: 'Model', maxConnections: 1, type: "${NodeConnectionType.AiLanguageModel}", required: true}] if (['insert', 'load', 'update'].includes(mode)) { inputs.push({ displayName: "", type: "${NodeConnectionType.Main}"}) @@ -381,24 +387,54 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) => ); } + // here vector stores supply data to parent nodes async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise { - const mode = this.getNodeParameter('mode', 0) as 'load' | 'insert' | 'retrieve'; - const filter = getMetadataFiltersValues(this, itemIndex); + // todo allow name and description to be configurable + const name = 'vector_store_tool'; //this.getNodeParameter('name', itemIndex) as string; + const toolDescription = 'get data'; //this.getNodeParameter('description', itemIndex) as string; + const topK = this.getNodeParameter('topK', itemIndex, 4) as number; + const embeddings = (await this.getInputConnectionData( NodeConnectionType.AiEmbedding, 0, )) as Embeddings; - if (mode === 'retrieve') { - const vectorStore = await args.getVectorStoreClient(this, filter, embeddings, itemIndex); - return { - response: logWrapper(vectorStore, this), - }; - } + const filter = getMetadataFiltersValues(this, itemIndex); + const vectorStore = await args.getVectorStoreClient(this, filter, embeddings, itemIndex); - throw new NodeOperationError( - this.getNode(), - 'Only the "retrieve" operation mode is supported to supply data', - ); + const llm = (await this.getInputConnectionData( + NodeConnectionType.AiLanguageModel, + 0, + )) as BaseLanguageModel; + + const description = VectorStoreQATool.getDescription(name, toolDescription); + const vectorStoreTool = new VectorStoreQATool(name, description, { + llm, + vectorStore, + }); + + vectorStoreTool.chain = VectorDBQAChain.fromLLM(llm, vectorStore, { + k: topK, + }); + + return { + response: logWrapper(vectorStoreTool, this), + }; + + // todo make this backward compatible, allowing to be used directly as a vector store + // const mode = this.getNodeParameter('mode', 0) as 'load' | 'insert' | 'retrieve'; + // const filter = getMetadataFiltersValues(this, itemIndex); + + // if (mode === 'retrieve') { + // const vectorStore = await args.getVectorStoreClient(this, filter, embeddings, itemIndex); + // return { + // response: logWrapper(vectorStore, this), + // }; + // } + + // throw new NodeOperationError( + // this.getNode(), + // 'Only the "retrieve" operation mode is supported to supply data', + // ); } }; diff --git a/packages/cli/src/node-types.ts b/packages/cli/src/node-types.ts index 553aedd620..708bc4c2cc 100644 --- a/packages/cli/src/node-types.ts +++ b/packages/cli/src/node-types.ts @@ -74,6 +74,7 @@ export class NodeTypes implements INodeTypes { const clonedNode = Object.create(versionedNodeType, { description: { value: clonedDescription }, }) as INodeType; + // convert nodes to tools as loading them const tool = NodeHelpers.convertNodeToAiTool(clonedNode); loadedNodes[nodeType + 'Tool'] = { sourcePath: '', type: tool }; return tool; diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 7ff1645335..415ddefa08 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -2238,12 +2238,15 @@ export async function getInputConnectionData( } try { + // here sub-node supplyData function gets called + // passing data to parent nodes const response = await nodeType.supplyData.call(context, itemIndex); if (response.closeFunction) { closeFunctions.push(response.closeFunction); } return response; } catch (error) { + console.log('e', error); // Propagate errors from sub-nodes if (error.functionality === 'configuration-node') throw error; if (!(error instanceof ExecutionBaseError)) { diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 97e63c96d8..fbcfa1d1b7 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -356,6 +356,7 @@ const declarativeNodeOptionParameters: INodeProperties = { * as an AI Agent Tool. * Returns the modified item (not copied) */ +// convert any node into ai tool export function convertNodeToAiTool< T extends object & { description: INodeTypeDescription | INodeTypeBaseDescription }, >(item: T): T { @@ -366,7 +367,8 @@ export function convertNodeToAiTool< if (isFullDescription(item.description)) { item.description.name += 'Tool'; - item.description.inputs = []; + // todo if vector store keep inputs + // item.description.inputs = []; item.description.outputs = [NodeConnectionType.AiTool]; item.description.displayName += ' Tool'; delete item.description.usableAsTool;