From 2021684e6a9e80c27463f70949f748c11269cae3 Mon Sep 17 00:00:00 2001 From: oleg Date: Fri, 7 Jun 2024 14:37:04 +0200 Subject: [PATCH] feat(OpenAI Node): Allow to select Image analyze model & improve types (#9660) Signed-off-by: Oleg Ivaniv --- .../actions/assistant/create.operation.ts | 2 +- .../actions/assistant/update.operation.ts | 2 +- .../vendors/OpenAi/actions/descriptions.ts | 6 +- .../OpenAi/actions/image/analyze.operation.ts | 11 ++- .../OpenAi/actions/text/message.operation.ts | 2 +- .../OpenAi/actions/versionDescription.ts | 2 +- .../vendors/OpenAi/methods/listSearch.ts | 86 ++++++++++++------- 7 files changed, 71 insertions(+), 40 deletions(-) diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts index 341a17712a0494..b9a0dee5359070 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/assistant/create.operation.ts @@ -9,7 +9,7 @@ import { apiRequest } from '../../transport'; import { modelRLC } from '../descriptions'; const properties: INodeProperties[] = [ - modelRLC, + modelRLC('modelSearch'), { displayName: 'Name', name: 'name', 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 061bb0165ae941..8a997aa99dcb79 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 @@ -67,7 +67,7 @@ const properties: INodeProperties[] = [ description: 'Whether to augments the assistant with knowledge from outside its model, such as proprietary product information or documents, find more here', }, - { ...modelRLC, required: false }, + { ...modelRLC('modelSearch'), required: false }, { displayName: 'Name', name: 'name', diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/descriptions.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/descriptions.ts index 8416e794d2bbc1..2f27ba64144dcc 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/descriptions.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/descriptions.ts @@ -1,6 +1,6 @@ import type { INodeProperties } from 'n8n-workflow'; -export const modelRLC: INodeProperties = { +export const modelRLC = (searchListMethod: string = 'modelSearch'): INodeProperties => ({ displayName: 'Model', name: 'modelId', type: 'resourceLocator', @@ -12,7 +12,7 @@ export const modelRLC: INodeProperties = { name: 'list', type: 'list', typeOptions: { - searchListMethod: 'modelSearch', + searchListMethod, searchable: true, }, }, @@ -23,7 +23,7 @@ export const modelRLC: INodeProperties = { placeholder: 'e.g. gpt-4', }, ], -}; +}); export const assistantRLC: INodeProperties = { displayName: 'Assistant', diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/image/analyze.operation.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/image/analyze.operation.ts index b29019602afb21..07a91ab82dad05 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/image/analyze.operation.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/image/analyze.operation.ts @@ -6,8 +6,13 @@ import type { } from 'n8n-workflow'; import { updateDisplayOptions, NodeOperationError } from 'n8n-workflow'; import { apiRequest } from '../../transport'; +import { modelRLC } from '../descriptions'; const properties: INodeProperties[] = [ + { + ...modelRLC('imageModelSearch'), + displayOptions: { show: { '@version': [{ _cnd: { gte: 1.4 } }] } }, + }, { displayName: 'Text Input', name: 'text', @@ -123,7 +128,11 @@ const displayOptions = { export const description = updateDisplayOptions(displayOptions, properties); export async function execute(this: IExecuteFunctions, i: number): Promise { - const model = 'gpt-4-vision-preview'; + let model = 'gpt-4-vision-preview'; + if (this.getNode().typeVersion >= 1.4) { + model = this.getNodeParameter('modelId', i, 'gpt-4o', { extractValue: true }) as string; + } + const text = this.getNodeParameter('text', i, '') as string; const inputType = this.getNodeParameter('inputType', i) as string; const options = this.getNodeParameter('options', i, {}); diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/text/message.operation.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/text/message.operation.ts index ddcb250d26e93b..4cf72e9f5f48c8 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/text/message.operation.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/text/message.operation.ts @@ -14,7 +14,7 @@ import { getConnectedTools } from '../../../../../utils/helpers'; import { MODELS_NOT_SUPPORT_FUNCTION_CALLS } from '../../helpers/constants'; const properties: INodeProperties[] = [ - modelRLC, + modelRLC('modelSearch'), { displayName: 'Messages', name: 'messages', diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts index 2ed98f89f9aa0e..45954f333bbbc1 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/actions/versionDescription.ts @@ -69,7 +69,7 @@ export const versionDescription: INodeTypeDescription = { name: 'openAi', icon: { light: 'file:openAi.svg', dark: 'file:openAi.dark.svg' }, group: ['transform'], - version: [1, 1.1, 1.2, 1.3], + version: [1, 1.1, 1.2, 1.3, 1.4], subtitle: `={{(${prettifyOperation})($parameter.resource, $parameter.operation)}}`, description: 'Message an assistant or GPT, analyze images, generate audio, etc.', defaults: { diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/methods/listSearch.ts b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/methods/listSearch.ts index 8ec3c53a2b25d2..9aa3633d453a13 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/methods/listSearch.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/OpenAi/methods/listSearch.ts @@ -5,6 +5,8 @@ import type { INodeListSearchResult, } from 'n8n-workflow'; +import type { Model } from 'openai/resources/models'; +import type { Assistant } from 'openai/resources/beta/assistants'; import { apiRequest } from '../transport'; export async function fileSearch( @@ -38,37 +40,52 @@ export async function fileSearch( } } -export async function modelSearch( - this: ILoadOptionsFunctions, - filter?: string, -): Promise { - let { data } = await apiRequest.call(this, 'GET', '/models'); +const getModelSearch = + (filterCondition: (model: Model) => boolean) => + async (ctx: ILoadOptionsFunctions, filter?: string): Promise => { + let { data } = (await apiRequest.call(ctx, 'GET', '/models')) as { data: Model[] }; - data = data?.filter((model: IDataObject) => (model.id as string).startsWith('gpt-')); + data = data?.filter((model) => filterCondition(model)); - let results: INodeListSearchItems[] = []; + let results: INodeListSearchItems[] = []; - if (filter) { - for (const model of data || []) { - if ((model.id as string)?.toLowerCase().includes(filter.toLowerCase())) { - results.push({ - name: (model.id as string).toUpperCase(), - value: model.id as string, - }); + if (filter) { + for (const model of data || []) { + if (model.id?.toLowerCase().includes(filter.toLowerCase())) { + results.push({ + name: model.id.toUpperCase(), + value: model.id, + }); + } } + } else { + results = (data || []).map((model) => ({ + name: model.id.toUpperCase(), + value: model.id, + })); } - } else { - results = (data || []).map((model: IDataObject) => ({ - name: (model.id as string).toUpperCase(), - value: model.id as string, - })); - } - results = results.sort((a, b) => a.name.localeCompare(b.name)); + results = results.sort((a, b) => a.name.localeCompare(b.name)); - return { - results, + return { + results, + }; }; + +export async function modelSearch( + this: ILoadOptionsFunctions, + filter?: string, +): Promise { + return await getModelSearch((model) => model.id.startsWith('gpt-'))(this, filter); +} + +export async function imageModelSearch( + this: ILoadOptionsFunctions, + filter?: string, +): Promise { + return await getModelSearch( + (model) => model.id.includes('vision') || model.id.includes('gpt-4o'), + )(this, filter); } export async function assistantSearch( @@ -76,7 +93,7 @@ export async function assistantSearch( filter?: string, paginationToken?: string, ): Promise { - const { data, has_more, last_id } = await apiRequest.call(this, 'GET', '/assistants', { + const { data, has_more, last_id } = (await apiRequest.call(this, 'GET', '/assistants', { headers: { 'OpenAI-Beta': 'assistants=v2', }, @@ -84,9 +101,14 @@ export async function assistantSearch( limit: 100, after: paginationToken, }, - }); + })) as { + data: Assistant[]; + has_more: boolean; + last_id: string; + first_id: string; + }; - if (has_more === true) { + if (has_more) { paginationToken = last_id; } else { paginationToken = undefined; @@ -96,10 +118,10 @@ export async function assistantSearch( const results: INodeListSearchItems[] = []; for (const assistant of data || []) { - if ((assistant.name as string)?.toLowerCase().includes(filter.toLowerCase())) { + if (assistant.name?.toLowerCase().includes(filter.toLowerCase())) { results.push({ - name: assistant.name as string, - value: assistant.id as string, + name: assistant.name, + value: assistant.id, }); } } @@ -109,9 +131,9 @@ export async function assistantSearch( }; } else { return { - results: (data || []).map((assistant: IDataObject) => ({ - name: assistant.name as string, - value: assistant.id as string, + results: (data || []).map((assistant) => ({ + name: assistant.name ?? assistant.id, + value: assistant.id, })), paginationToken, };