From 8a8eb241cfa0f5366720e1affec8e75a7ebaaa4d Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour Date: Wed, 4 Dec 2024 14:14:35 +0100 Subject: [PATCH 1/4] fix(OpenAi Node): Allow updating assistant files --- .../actions/assistant/update.operation.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts index 8a997aa99dcb7..00a1e36a67048 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts @@ -4,7 +4,7 @@ import type { INodeExecutionData, IDataObject, } from 'n8n-workflow'; -import { NodeOperationError, updateDisplayOptions } from 'n8n-workflow'; +import { ApplicationError, NodeOperationError, updateDisplayOptions } from 'n8n-workflow'; import { apiRequest } from '../../transport'; import { assistantRLC, modelRLC } from '../descriptions'; @@ -116,6 +116,18 @@ const displayOptions = { export const description = updateDisplayOptions(displayOptions, properties); +function getFileIds(file_ids: unknown): string[] { + if (Array.isArray(file_ids)) { + return file_ids; + } + + if (typeof file_ids === 'string') { + return file_ids.split(',').map((file_id) => file_id.trim()); + } + + throw new ApplicationError('Invalid file_ids type'); +} + export async function execute(this: IExecuteFunctions, i: number): Promise { const assistantId = this.getNodeParameter('assistantId', i, '', { extractValue: true }) as string; const options = this.getNodeParameter('options', i, {}); @@ -137,11 +149,8 @@ export async function execute(this: IExecuteFunctions, i: number): Promise file_id.trim()); - } - if ((file_ids as IDataObject[]).length > 20) { + const files = getFileIds(file_ids); + if (files.length > 20) { throw new NodeOperationError( this.getNode(), 'The maximum number of files that can be attached to the assistant is 20', @@ -152,14 +161,7 @@ export async function execute(this: IExecuteFunctions, i: number): Promise Date: Wed, 4 Dec 2024 15:45:43 +0100 Subject: [PATCH 2/4] chore: comment explaining --- .../nodes/vendors/OpenAi/actions/assistant/update.operation.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts index 00a1e36a67048..c040414e5815c 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts @@ -163,6 +163,9 @@ export async function execute(this: IExecuteFunctions, i: number): Promise Date: Wed, 4 Dec 2024 15:51:15 +0100 Subject: [PATCH 3/4] chore: comment explaining --- .../vendors/OpenAi/actions/assistant/update.operation.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts index c040414e5815c..bd26ab0a0c5b0 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/update.operation.ts @@ -163,8 +163,9 @@ export async function execute(this: IExecuteFunctions, i: number): Promise Date: Thu, 5 Dec 2024 17:30:06 +0100 Subject: [PATCH 4/4] update tests --- .../vendors/OpenAi/test/OpenAi.node.test.ts | 89 +++++++++++++++++-- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/test/OpenAi.node.test.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/test/OpenAi.node.test.ts index 211b99166c0d3..a78260844c87a 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/test/OpenAi.node.test.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/test/OpenAi.node.test.ts @@ -210,12 +210,89 @@ describe('OpenAi, Assistant resource', () => { code_interpreter: { file_ids: [], }, - file_search: { - vector_stores: [ - { - file_ids: [], - }, - ], + }, + tools: [{ type: 'existing_tool' }, { type: 'code_interpreter' }, { type: 'file_search' }], + }, + headers: { 'OpenAI-Beta': 'assistants=v2' }, + }); + }); + + it('update => should call apiRequest with file_ids as an array for search', async () => { + (transport.apiRequest as jest.Mock).mockResolvedValueOnce({ + tools: [{ type: 'existing_tool' }], + }); + (transport.apiRequest as jest.Mock).mockResolvedValueOnce({}); + + await assistant.update.execute.call( + createExecuteFunctionsMock({ + assistantId: 'assistant-id', + options: { + modelId: 'gpt-model', + name: 'name', + instructions: 'some instructions', + codeInterpreter: true, + knowledgeRetrieval: true, + file_ids: ['1234'], + removeCustomTools: false, + }, + }), + 0, + ); + + expect(transport.apiRequest).toHaveBeenCalledTimes(2); + expect(transport.apiRequest).toHaveBeenCalledWith('GET', '/assistants/assistant-id', { + headers: { 'OpenAI-Beta': 'assistants=v2' }, + }); + expect(transport.apiRequest).toHaveBeenCalledWith('POST', '/assistants/assistant-id', { + body: { + instructions: 'some instructions', + model: 'gpt-model', + name: 'name', + tool_resources: { + code_interpreter: { + file_ids: ['1234'], + }, + }, + tools: [{ type: 'existing_tool' }, { type: 'code_interpreter' }, { type: 'file_search' }], + }, + headers: { 'OpenAI-Beta': 'assistants=v2' }, + }); + }); + + it('update => should call apiRequest with file_ids as strings for search', async () => { + (transport.apiRequest as jest.Mock).mockResolvedValueOnce({ + tools: [{ type: 'existing_tool' }], + }); + (transport.apiRequest as jest.Mock).mockResolvedValueOnce({}); + + await assistant.update.execute.call( + createExecuteFunctionsMock({ + assistantId: 'assistant-id', + options: { + modelId: 'gpt-model', + name: 'name', + instructions: 'some instructions', + codeInterpreter: true, + knowledgeRetrieval: true, + file_ids: '1234, 5678, 90', + removeCustomTools: false, + }, + }), + 0, + ); + + expect(transport.apiRequest).toHaveBeenCalledTimes(2); + expect(transport.apiRequest).toHaveBeenCalledWith('GET', '/assistants/assistant-id', { + headers: { 'OpenAI-Beta': 'assistants=v2' }, + }); + expect(transport.apiRequest).toHaveBeenCalledWith('POST', '/assistants/assistant-id', { + body: { + instructions: 'some instructions', + model: 'gpt-model', + name: 'name', + tool_resources: { + code_interpreter: { + file_ids: ['1234', '5678', '90'], }, }, tools: [{ type: 'existing_tool' }, { type: 'code_interpreter' }, { type: 'file_search' }],