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}']);
const prompt = ChatPromptTemplate.fromMessages(messages);
// here agent is created first step with tools
const agent = createToolCallingAgent({
llm: model,
tools,

View file

@ -2,7 +2,10 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import type { Document } from '@langchain/core/documents';
import type { Embeddings } from '@langchain/core/embeddings';
import type { BaseLanguageModel } from '@langchain/core/language_models/base';
import type { VectorStore } from '@langchain/core/vectorstores';
import { VectorDBQAChain } from 'langchain/chains';
import { VectorStoreQATool } from 'langchain/tools';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
import type {
IExecuteFunctions,
@ -129,6 +132,7 @@ export const createVectorStoreNode = (args: VectorStoreNodeConstructorArgs) =>
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<SupplyData> {
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 filter = getMetadataFiltersValues(this, itemIndex);
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',
);
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',
// );
}
};

View file

@ -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;

View file

@ -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)) {

View file

@ -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;