spike/poc work

This commit is contained in:
Mutasem Aldmour 2024-12-05 17:23:42 +01:00
parent 706702dff8
commit 28f87d7ecb
No known key found for this signature in database
GPG key ID: 3DFA8122BB7FD6B8
5 changed files with 57 additions and 14 deletions

View file

@ -270,6 +270,7 @@ export async function toolsAgentExecute(this: IExecuteFunctions): Promise<INodeE
messages.push(['placeholder', '{agent_scratchpad}']); messages.push(['placeholder', '{agent_scratchpad}']);
const prompt = ChatPromptTemplate.fromMessages(messages); const prompt = ChatPromptTemplate.fromMessages(messages);
// here agent is created first step with tools
const agent = createToolCallingAgent({ const agent = createToolCallingAgent({
llm: model, llm: model,
tools, tools,

View file

@ -2,7 +2,10 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { Document } from '@langchain/core/documents'; import type { Document } from '@langchain/core/documents';
import type { Embeddings } from '@langchain/core/embeddings'; import type { Embeddings } from '@langchain/core/embeddings';
import type { BaseLanguageModel } from '@langchain/core/language_models/base';
import type { VectorStore } from '@langchain/core/vectorstores'; import type { VectorStore } from '@langchain/core/vectorstores';
import { VectorDBQAChain } from 'langchain/chains';
import { VectorStoreQATool } from 'langchain/tools';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import type { import type {
IExecuteFunctions, IExecuteFunctions,
@ -129,6 +132,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
defaults: { defaults: {
name: args.meta.displayName, name: args.meta.displayName,
}, },
usableAsTool: true,
codex: { codex: {
categories: ['AI'], categories: ['AI'],
subcategories: { subcategories: {
@ -143,11 +147,13 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
}, },
}, },
credentials: args.meta.credentials, 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 // eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: `={{ inputs: `={{
((parameters) => { ((parameters) => {
const mode = parameters?.mode; 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)) { if (['insert', 'load', 'update'].includes(mode)) {
inputs.push({ displayName: "", type: "${NodeConnectionType.Main}"}) 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<SupplyData> { async supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData> {
const mode = this.getNodeParameter('mode', 0) as 'load' | 'insert' | 'retrieve'; // todo allow name and description to be configurable
const filter = getMetadataFiltersValues(this, itemIndex); 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( const embeddings = (await this.getInputConnectionData(
NodeConnectionType.AiEmbedding, NodeConnectionType.AiEmbedding,
0, 0,
)) as Embeddings; )) as Embeddings;
if (mode === 'retrieve') { const filter = getMetadataFiltersValues(this, itemIndex);
const vectorStore = await args.getVectorStoreClient(this, filter, embeddings, itemIndex); const vectorStore = await args.getVectorStoreClient(this, filter, embeddings, itemIndex);
return {
response: logWrapper(vectorStore, this),
};
}
throw new NodeOperationError( const llm = (await this.getInputConnectionData(
this.getNode(), NodeConnectionType.AiLanguageModel,
'Only the "retrieve" operation mode is supported to supply data', 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',
// );
} }
}; };

View file

@ -74,6 +74,7 @@ export class NodeTypes implements INodeTypes {
const clonedNode = Object.create(versionedNodeType, { const clonedNode = Object.create(versionedNodeType, {
description: { value: clonedDescription }, description: { value: clonedDescription },
}) as INodeType; }) as INodeType;
// convert nodes to tools as loading them
const tool = NodeHelpers.convertNodeToAiTool(clonedNode); const tool = NodeHelpers.convertNodeToAiTool(clonedNode);
loadedNodes[nodeType + 'Tool'] = { sourcePath: '', type: tool }; loadedNodes[nodeType + 'Tool'] = { sourcePath: '', type: tool };
return tool; return tool;

View file

@ -2238,12 +2238,15 @@ export async function getInputConnectionData(
} }
try { try {
// here sub-node supplyData function gets called
// passing data to parent nodes
const response = await nodeType.supplyData.call(context, itemIndex); const response = await nodeType.supplyData.call(context, itemIndex);
if (response.closeFunction) { if (response.closeFunction) {
closeFunctions.push(response.closeFunction); closeFunctions.push(response.closeFunction);
} }
return response; return response;
} catch (error) { } catch (error) {
console.log('e', error);
// Propagate errors from sub-nodes // Propagate errors from sub-nodes
if (error.functionality === 'configuration-node') throw error; if (error.functionality === 'configuration-node') throw error;
if (!(error instanceof ExecutionBaseError)) { if (!(error instanceof ExecutionBaseError)) {

View file

@ -356,6 +356,7 @@ const declarativeNodeOptionParameters: INodeProperties = {
* as an AI Agent Tool. * as an AI Agent Tool.
* Returns the modified item (not copied) * Returns the modified item (not copied)
*/ */
// convert any node into ai tool
export function convertNodeToAiTool< export function convertNodeToAiTool<
T extends object & { description: INodeTypeDescription | INodeTypeBaseDescription }, T extends object & { description: INodeTypeDescription | INodeTypeBaseDescription },
>(item: T): T { >(item: T): T {
@ -366,7 +367,8 @@ export function convertNodeToAiTool<
if (isFullDescription(item.description)) { if (isFullDescription(item.description)) {
item.description.name += 'Tool'; item.description.name += 'Tool';
item.description.inputs = []; // todo if vector store keep inputs
// 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;