diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts index 4b2ddf5db9..45d3932309 100644 --- a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts @@ -34,6 +34,7 @@ import { getOptionalOutputParsers } from '@utils/output_parsers/N8nOutputParser' import { getTemplateNoticeField } from '@utils/sharedFields'; import { getTracingConfig } from '@utils/tracing'; +import { dataUriFromImageData, UnsupportedMimeTypeError } from './utils'; import { getCustomErrorMessage as getCustomOpenAiErrorMessage, isOpenAiError, @@ -88,21 +89,28 @@ async function getImageMessage( NodeConnectionType.AiLanguageModel, 0, )) as BaseLanguageModel; - const dataURI = `data:image/jpeg;base64,${bufferData.toString('base64')}`; - const directUriModels = [ChatGoogleGenerativeAI, ChatOllama]; - const imageUrl = directUriModels.some((i) => model instanceof i) - ? dataURI - : { url: dataURI, detail }; + try { + const dataURI = dataUriFromImageData(binaryData, bufferData); - return new HumanMessage({ - content: [ - { - type: 'image_url', - image_url: imageUrl, - }, - ], - }); + const directUriModels = [ChatGoogleGenerativeAI, ChatOllama]; + const imageUrl = directUriModels.some((i) => model instanceof i) + ? dataURI + : { url: dataURI, detail }; + + return new HumanMessage({ + content: [ + { + type: 'image_url', + image_url: imageUrl, + }, + ], + }); + } catch (error) { + if (error instanceof UnsupportedMimeTypeError) + throw new NodeOperationError(context.getNode(), error.message); + throw error; + } } async function getChainPromptTemplate( diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/test/utils.test.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/test/utils.test.ts new file mode 100644 index 0000000000..2207caf20b --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/test/utils.test.ts @@ -0,0 +1,23 @@ +import { mock } from 'jest-mock-extended'; +import type { IBinaryData } from 'n8n-workflow'; + +import { dataUriFromImageData, UnsupportedMimeTypeError } from '../utils'; + +describe('dataUriFromImageData', () => { + it('should not throw an error on images', async () => { + const mockBuffer = Buffer.from('Test data'); + const mockBinaryData = mock({ mimeType: 'image/jpeg' }); + + const dataUri = dataUriFromImageData(mockBinaryData, mockBuffer); + expect(dataUri).toBe('data:image/jpeg;base64,VGVzdCBkYXRh'); + }); + + it('should throw an UnsupportetMimeTypeError on non-images', async () => { + const mockBuffer = Buffer.from('Test data'); + const mockBinaryData = mock({ mimeType: 'text/plain' }); + + expect(() => { + dataUriFromImageData(mockBinaryData, mockBuffer); + }).toThrow(UnsupportedMimeTypeError); + }); +}); diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/utils.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/utils.ts new file mode 100644 index 0000000000..95671d40fe --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/utils.ts @@ -0,0 +1,12 @@ +import type { IBinaryData } from 'n8n-workflow'; +import { ApplicationError } from 'n8n-workflow'; + +export class UnsupportedMimeTypeError extends ApplicationError {} + +export function dataUriFromImageData(binaryData: IBinaryData, bufferData: Buffer) { + if (!binaryData.mimeType?.startsWith('image/')) + throw new UnsupportedMimeTypeError( + `${binaryData.mimeType} is not a supported type of binary data. Only images are supported.`, + ); + return `data:${binaryData.mimeType};base64,${bufferData.toString('base64')}`; +}