first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
+79
@@ -0,0 +1,79 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import pick from 'lodash/pick';
|
||||
import type { WorkflowTestData } from 'n8n-workflow';
|
||||
import path from 'node:path';
|
||||
|
||||
describe('OpenAI Workflow', () => {
|
||||
const baseUrl = 'https://api.openai.com/v1';
|
||||
const credentials = {
|
||||
openAiApi: { url: baseUrl },
|
||||
};
|
||||
|
||||
const testHarness = new NodeTestHarness({
|
||||
additionalPackagePaths: [path.dirname(require.resolve('n8n-nodes-base'))],
|
||||
});
|
||||
|
||||
const assistants = [
|
||||
{
|
||||
id: 'asst_abc123',
|
||||
object: 'assistant',
|
||||
created_at: 1698982736,
|
||||
name: 'Coding Tutor',
|
||||
description: null,
|
||||
model: 'gpt-4o',
|
||||
tools: [],
|
||||
tool_resources: {},
|
||||
metadata: {},
|
||||
top_p: 1.0,
|
||||
temperature: 1.0,
|
||||
response_format: 'auto',
|
||||
},
|
||||
{
|
||||
id: 'asst_abc456',
|
||||
object: 'assistant',
|
||||
created_at: 1698982718,
|
||||
name: 'My Assistant',
|
||||
description: null,
|
||||
model: 'gpt-4o',
|
||||
tools: [],
|
||||
tool_resources: {},
|
||||
metadata: {},
|
||||
top_p: 1.0,
|
||||
temperature: 1.0,
|
||||
response_format: 'auto',
|
||||
},
|
||||
];
|
||||
|
||||
const testData: WorkflowTestData = {
|
||||
description: 'List Assistants',
|
||||
input: {
|
||||
workflowData: testHarness.readWorkflowJSON('list-assistants.workflow.json'),
|
||||
},
|
||||
output: {
|
||||
nodeExecutionOrder: ['When clicking ‘Execute workflow’', 'OpenAI'],
|
||||
nodeData: {
|
||||
OpenAI: [
|
||||
assistants.map((assistant) => ({
|
||||
json: pick(assistant, ['id', 'model', 'name']),
|
||||
})),
|
||||
],
|
||||
},
|
||||
},
|
||||
nock: {
|
||||
baseUrl,
|
||||
mocks: [
|
||||
{
|
||||
method: 'get',
|
||||
path: '/assistants?limit=100',
|
||||
statusCode: 200,
|
||||
responseBody: {
|
||||
object: 'list',
|
||||
data: assistants,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
testHarness.setupTest(testData, { credentials });
|
||||
});
|
||||
+639
@@ -0,0 +1,639 @@
|
||||
import FormData from 'form-data';
|
||||
import get from 'lodash/get';
|
||||
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import * as assistant from '../../v1/actions/assistant';
|
||||
import * as audio from '../../v1/actions/audio';
|
||||
import * as file from '../../v1/actions/file';
|
||||
import * as image from '../../v1/actions/image';
|
||||
import * as text from '../../v1/actions/text';
|
||||
import * as transport from '../../transport';
|
||||
|
||||
const createExecuteFunctionsMock = (parameters: IDataObject) => {
|
||||
const nodeParameters = parameters;
|
||||
return {
|
||||
getExecutionCancelSignal() {
|
||||
return new AbortController().signal;
|
||||
},
|
||||
getNodeParameter(parameter: string) {
|
||||
return get(nodeParameters, parameter);
|
||||
},
|
||||
getNode() {
|
||||
return {};
|
||||
},
|
||||
getInputConnectionData() {
|
||||
return undefined;
|
||||
},
|
||||
helpers: {
|
||||
prepareBinaryData() {
|
||||
return {};
|
||||
},
|
||||
assertBinaryData() {
|
||||
return {
|
||||
filename: 'filenale.flac',
|
||||
contentType: 'audio/flac',
|
||||
};
|
||||
},
|
||||
getBinaryDataBuffer() {
|
||||
return 'data buffer data';
|
||||
},
|
||||
},
|
||||
} as unknown as IExecuteFunctions;
|
||||
};
|
||||
|
||||
describe('OpenAi, Assistant resource', () => {
|
||||
beforeEach(() => {
|
||||
(transport as any).apiRequest = jest.fn();
|
||||
});
|
||||
|
||||
it('create => should throw an error if an assistant with the same name already exists', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({
|
||||
data: [{ name: 'name' }],
|
||||
has_more: false,
|
||||
});
|
||||
|
||||
try {
|
||||
await assistant.create.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
name: 'name',
|
||||
options: {
|
||||
failIfExists: true,
|
||||
},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(error.message).toBe("An assistant with the same name 'name' already exists");
|
||||
}
|
||||
});
|
||||
|
||||
it('create => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({});
|
||||
|
||||
await assistant.create.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
modelId: 'gpt-model',
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
instructions: 'some instructions',
|
||||
codeInterpreter: true,
|
||||
knowledgeRetrieval: true,
|
||||
file_ids: [],
|
||||
options: {},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', '/assistants', {
|
||||
body: {
|
||||
description: 'description',
|
||||
instructions: 'some instructions',
|
||||
model: 'gpt-model',
|
||||
name: 'name',
|
||||
tool_resources: {
|
||||
code_interpreter: {
|
||||
file_ids: [],
|
||||
},
|
||||
file_search: {
|
||||
vector_stores: [
|
||||
{
|
||||
file_ids: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
tools: [{ type: 'code_interpreter' }, { type: 'file_search' }],
|
||||
},
|
||||
headers: { 'OpenAI-Beta': 'assistants=v2' },
|
||||
});
|
||||
});
|
||||
|
||||
it('create => should throw error if more then 20 files selected', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({});
|
||||
|
||||
try {
|
||||
await assistant.create.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
file_ids: Array.from({ length: 25 }),
|
||||
options: {},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
expect(true).toBe(false);
|
||||
} catch (error) {
|
||||
expect(error.message).toBe(
|
||||
'The maximum number of files that can be attached to the assistant is 20',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('delete => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({});
|
||||
|
||||
await assistant.deleteAssistant.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
assistantId: 'assistant-id',
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('DELETE', '/assistants/assistant-id', {
|
||||
headers: { 'OpenAI-Beta': 'assistants=v2' },
|
||||
});
|
||||
});
|
||||
|
||||
it('list => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({
|
||||
data: [
|
||||
{ name: 'name1', id: 'id-1', model: 'gpt-model', other: 'other' },
|
||||
{ name: 'name2', id: 'id-2', model: 'gpt-model', other: 'other' },
|
||||
{ name: 'name3', id: 'id-3', model: 'gpt-model', other: 'other' },
|
||||
],
|
||||
has_more: false,
|
||||
});
|
||||
|
||||
const response = await assistant.list.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
simplify: true,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(response).toEqual([
|
||||
{
|
||||
json: { name: 'name1', id: 'id-1', model: 'gpt-model' },
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
{
|
||||
json: { name: 'name2', id: 'id-2', model: 'gpt-model' },
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
{
|
||||
json: { name: 'name3', id: 'id-3', model: 'gpt-model' },
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('update => should call apiRequest with correct parameters', 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: [],
|
||||
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: [],
|
||||
},
|
||||
},
|
||||
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' }],
|
||||
},
|
||||
headers: { 'OpenAI-Beta': 'assistants=v2' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenAi, Audio resource', () => {
|
||||
beforeEach(() => {
|
||||
(transport as any).apiRequest = jest.fn();
|
||||
});
|
||||
|
||||
it('generate => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({});
|
||||
|
||||
const returnData = await audio.generate.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
model: 'tts-model',
|
||||
input: 'input',
|
||||
voice: 'fable',
|
||||
options: {
|
||||
response_format: 'flac',
|
||||
speed: 1.25,
|
||||
binaryPropertyOutput: 'myData',
|
||||
},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(returnData.length).toEqual(1);
|
||||
expect(returnData[0].binary?.myData).toBeDefined();
|
||||
expect(returnData[0].pairedItem).toBeDefined();
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', '/audio/speech', {
|
||||
body: {
|
||||
input: 'input',
|
||||
model: 'tts-model',
|
||||
response_format: 'flac',
|
||||
speed: 1.25,
|
||||
voice: 'fable',
|
||||
},
|
||||
option: { encoding: 'arraybuffer', json: false, returnFullResponse: true, useStream: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('transcribe => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({ text: 'transcribtion' });
|
||||
|
||||
const returnData = await audio.transcribe.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
binaryPropertyName: 'myData',
|
||||
options: {
|
||||
language: 'en',
|
||||
temperature: 1.1,
|
||||
},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(returnData.length).toEqual(1);
|
||||
expect(returnData[0].pairedItem).toBeDefined();
|
||||
expect(returnData[0].json).toEqual({ text: 'transcribtion' });
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/audio/transcriptions',
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'content-type': expect.stringMatching(/^multipart\/form-data; boundary=/),
|
||||
}),
|
||||
option: expect.objectContaining({
|
||||
formData: expect.any(FormData),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('translate => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({ text: 'translations' });
|
||||
|
||||
const returnData = await audio.translate.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
binaryPropertyName: 'myData',
|
||||
options: {},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(returnData.length).toEqual(1);
|
||||
expect(returnData[0].pairedItem).toBeDefined();
|
||||
expect(returnData[0].json).toEqual({ text: 'translations' });
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/audio/translations',
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'content-type': expect.stringMatching(/^multipart\/form-data; boundary=/),
|
||||
}),
|
||||
option: expect.objectContaining({
|
||||
formData: expect.any(FormData),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenAi, File resource', () => {
|
||||
beforeEach(() => {
|
||||
(transport as any).apiRequest = jest.fn();
|
||||
});
|
||||
|
||||
it('deleteFile => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({});
|
||||
|
||||
await file.deleteFile.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
fileId: 'file-id',
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('DELETE', '/files/file-id');
|
||||
});
|
||||
|
||||
it('list => should return list of files', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({
|
||||
data: [{ file: 'file1' }, { file: 'file2' }, { file: 'file3' }],
|
||||
});
|
||||
|
||||
const returnData = await file.list.execute.call(createExecuteFunctionsMock({ options: {} }), 2);
|
||||
|
||||
expect(returnData.length).toEqual(3);
|
||||
expect(returnData).toEqual([
|
||||
{
|
||||
json: { file: 'file1' },
|
||||
pairedItem: { item: 2 },
|
||||
},
|
||||
{
|
||||
json: { file: 'file2' },
|
||||
pairedItem: { item: 2 },
|
||||
},
|
||||
{
|
||||
json: { file: 'file3' },
|
||||
pairedItem: { item: 2 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('upload => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({ success: true });
|
||||
|
||||
const returnData = await file.upload.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
binaryPropertyName: 'myData',
|
||||
options: {},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(returnData.length).toEqual(1);
|
||||
expect(returnData[0].pairedItem).toBeDefined();
|
||||
expect(returnData[0].json).toEqual({ success: true });
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/files',
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
'content-type': expect.stringMatching(/^multipart\/form-data; boundary=/),
|
||||
}),
|
||||
option: expect.objectContaining({
|
||||
formData: expect.any(FormData),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenAi, Image resource', () => {
|
||||
beforeEach(() => {
|
||||
(transport as any).apiRequest = jest.fn();
|
||||
});
|
||||
|
||||
it('generate => should call apiRequest with correct parameters, return binary', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({ data: [{ b64_json: 'image1' }] });
|
||||
|
||||
const returnData = await image.generate.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
model: 'dall-e-3',
|
||||
prompt: 'cat with a hat',
|
||||
options: {
|
||||
size: '1024x1024',
|
||||
style: 'vivid',
|
||||
quality: 'hd',
|
||||
binaryPropertyOutput: 'myData',
|
||||
},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(returnData.length).toEqual(1);
|
||||
expect(returnData[0].binary?.myData).toBeDefined();
|
||||
expect(returnData[0].pairedItem).toBeDefined();
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', '/images/generations', {
|
||||
body: {
|
||||
model: 'dall-e-3',
|
||||
prompt: 'cat with a hat',
|
||||
quality: 'hd',
|
||||
response_format: 'b64_json',
|
||||
size: '1024x1024',
|
||||
style: 'vivid',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('generate => should call apiRequest with correct parameters, return urls', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({ data: [{ url: 'image-url' }] });
|
||||
|
||||
const returnData = await image.generate.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
model: 'dall-e-3',
|
||||
prompt: 'cat with a hat',
|
||||
options: {
|
||||
size: '1024x1024',
|
||||
style: 'vivid',
|
||||
quality: 'hd',
|
||||
binaryPropertyOutput: 'myData',
|
||||
returnImageUrls: true,
|
||||
},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(returnData.length).toEqual(1);
|
||||
expect(returnData[0].pairedItem).toBeDefined();
|
||||
expect(returnData).toEqual([{ json: { url: 'image-url' }, pairedItem: { item: 0 } }]);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', '/images/generations', {
|
||||
body: {
|
||||
model: 'dall-e-3',
|
||||
prompt: 'cat with a hat',
|
||||
quality: 'hd',
|
||||
response_format: 'url',
|
||||
size: '1024x1024',
|
||||
style: 'vivid',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('analyze => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({ success: true });
|
||||
|
||||
const returnData = await image.analyze.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
text: 'image text',
|
||||
inputType: 'url',
|
||||
imageUrls: 'image-url1, image-url2',
|
||||
options: {
|
||||
detail: 'low',
|
||||
},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(returnData.length).toEqual(1);
|
||||
expect(returnData[0].pairedItem).toBeDefined();
|
||||
expect(returnData[0].json).toEqual({ success: true });
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', '/chat/completions', {
|
||||
body: {
|
||||
max_tokens: 300,
|
||||
messages: [
|
||||
{
|
||||
content: [
|
||||
{ text: 'image text', type: 'text' },
|
||||
{ image_url: { detail: 'low', url: 'image-url1' }, type: 'image_url' },
|
||||
{ image_url: { detail: 'low', url: 'image-url2' }, type: 'image_url' },
|
||||
],
|
||||
role: 'user',
|
||||
},
|
||||
],
|
||||
model: 'gpt-4-vision-preview',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenAi, Text resource', () => {
|
||||
beforeEach(() => {
|
||||
(transport as any).apiRequest = jest.fn();
|
||||
});
|
||||
|
||||
it('classify => should call apiRequest with correct parameters', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({ results: [{ flagged: true }] });
|
||||
|
||||
const returnData = await text.classify.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
input: 'input',
|
||||
options: { useStableModel: true },
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(returnData.length).toEqual(1);
|
||||
expect(returnData[0].pairedItem).toBeDefined();
|
||||
expect(returnData[0].json).toEqual({ flagged: true });
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', '/moderations', {
|
||||
body: { input: 'input', model: 'text-moderation-stable' },
|
||||
});
|
||||
});
|
||||
|
||||
it('message => should call apiRequest with correct parameters, no tool call', async () => {
|
||||
(transport.apiRequest as jest.Mock).mockResolvedValueOnce({
|
||||
choices: [{ message: { tool_calls: undefined } }],
|
||||
});
|
||||
|
||||
await text.message.execute.call(
|
||||
createExecuteFunctionsMock({
|
||||
modelId: 'gpt-model',
|
||||
messages: {
|
||||
values: [{ role: 'user', content: 'message' }],
|
||||
},
|
||||
|
||||
options: {},
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
expect(transport.apiRequest).toHaveBeenCalledWith('POST', '/chat/completions', {
|
||||
body: {
|
||||
messages: [{ content: 'message', role: 'user' }],
|
||||
model: 'gpt-model',
|
||||
response_format: undefined,
|
||||
tools: undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "ce6133c3-2eb6-4262-8e0c-54015ed0f795",
|
||||
"name": "When clicking ‘Execute workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "assistant",
|
||||
"operation": "list"
|
||||
},
|
||||
"type": "@n8n/n8n-nodes-langchain.openAi",
|
||||
"typeVersion": 1.8,
|
||||
"position": [220, 0],
|
||||
"id": "070d2fcc-032c-4c3f-ae33-80a5352785f8",
|
||||
"name": "OpenAI",
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "123",
|
||||
"name": "OpenAi account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "OpenAI",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user