feat(Mistral AI Node): New node (#16631)

Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
RomanDavydchuk
2025-07-01 09:19:01 +03:00
committed by GitHub
parent 9517d11a7e
commit c11e4bd0a8
15 changed files with 1843 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
import path from 'path';
import batchResult from './fixtures/batch.json';
import documentResult from './fixtures/document.json';
import imageResult from './fixtures/image.json';
describe('Mistral AI Node', () => {
const credentials = {
mistralCloudApi: {
apiKey: 'API-KEY',
},
};
const mistralAiNock = nock('https://api.mistral.ai');
describe('Document -> Extract Text', () => {
beforeAll(() => {
// Document by URL
mistralAiNock
.post('/v1/ocr', {
model: 'mistral-ocr-latest',
document: {
type: 'document_url',
document_url: 'https://example.com/document.pdf',
},
})
.reply(200, documentResult);
// Image by URL
mistralAiNock
.post('/v1/ocr', {
model: 'mistral-ocr-latest',
document: {
type: 'image_url',
image_url: 'https://example.com/image.jpg',
},
})
.reply(200, imageResult);
// Document from binary
mistralAiNock
.post('/v1/ocr', {
model: 'mistral-ocr-latest',
document: {
type: 'document_url',
document_name: 'sample.pdf',
document_url: 'data:application/pdf;base64,abcdefgh',
},
})
.reply(200, documentResult);
// Image from binary
mistralAiNock
.post('/v1/ocr', {
model: 'mistral-ocr-latest',
document: {
type: 'image_url',
image_url: 'data:image/jpeg;base64,abcdefgh',
},
})
.reply(200, imageResult);
// Batching
mistralAiNock
.post(
'/v1/files',
(body: string) =>
body.includes(
JSON.stringify({
document: {
type: 'document_url',
document_name: 'sample_1.pdf',
document_url: 'data:application/pdf;base64,abcdefgh',
},
}),
) &&
body.includes(
JSON.stringify({
document: {
type: 'document_url',
document_name: 'sample_2.pdf',
document_url: 'data:application/pdf;base64,aaaaaaaa',
},
}),
),
)
.reply(200, { id: 'input-file-1' });
mistralAiNock
.post('/v1/files', (body: string) =>
body.includes(
JSON.stringify({
document: {
type: 'document_url',
document_name: 'sample_3.pdf',
document_url: 'data:application/pdf;base64,aaaabbbb',
},
}),
),
)
.reply(200, { id: 'input-file-2' });
mistralAiNock
.post('/v1/batch/jobs', {
model: 'mistral-ocr-latest',
input_files: ['input-file-1'],
endpoint: '/v1/ocr',
})
.reply(200, { id: 'job-1' });
mistralAiNock
.post('/v1/batch/jobs', {
model: 'mistral-ocr-latest',
input_files: ['input-file-2'],
endpoint: '/v1/ocr',
})
.reply(200, { id: 'job-2' });
mistralAiNock.get('/v1/batch/jobs/job-1').reply(200, {
status: 'SUCCESS',
output_file: 'output-file-1',
});
mistralAiNock.get('/v1/batch/jobs/job-2').reply(200, {
status: 'SUCCESS',
output_file: 'output-file-2',
});
mistralAiNock.get('/v1/files/output-file-1/content').reply(
200,
batchResult
.slice(0, 2)
.map((item) => JSON.stringify(item))
.join('\n'),
);
mistralAiNock.get('/v1/files/output-file-2/content').reply(200, batchResult[2]);
// Batching with delete files
mistralAiNock
.post(
'/v1/files',
(body: string) =>
body.includes(
JSON.stringify({
document: {
type: 'document_url',
document_name: 'sample_1.pdf',
document_url: 'data:application/pdf;base64,abcdefgh',
},
}),
) &&
body.includes(
JSON.stringify({
document: {
type: 'document_url',
document_name: 'sample_2.pdf',
document_url: 'data:application/pdf;base64,aaaaaaaa',
},
}),
),
)
.reply(200, { id: 'input-file-1' });
mistralAiNock
.post('/v1/files', (body: string) =>
body.includes(
JSON.stringify({
document: {
type: 'document_url',
document_name: 'sample_3.pdf',
document_url: 'data:application/pdf;base64,aaaabbbb',
},
}),
),
)
.reply(200, { id: 'input-file-2' });
mistralAiNock
.post('/v1/batch/jobs', {
model: 'mistral-ocr-latest',
input_files: ['input-file-1'],
endpoint: '/v1/ocr',
})
.reply(200, { id: 'job-1' });
mistralAiNock
.post('/v1/batch/jobs', {
model: 'mistral-ocr-latest',
input_files: ['input-file-2'],
endpoint: '/v1/ocr',
})
.reply(200, { id: 'job-2' });
mistralAiNock.get('/v1/batch/jobs/job-1').reply(200, {
status: 'SUCCESS',
output_file: 'output-file-1',
});
mistralAiNock.get('/v1/batch/jobs/job-2').reply(200, {
status: 'SUCCESS',
output_file: 'output-file-2',
});
mistralAiNock.delete('/v1/files/input-file-1').reply(200);
mistralAiNock.delete('/v1/files/input-file-2').reply(200);
mistralAiNock.get('/v1/files/output-file-1/content').reply(
200,
batchResult
.slice(0, 2)
.map((item) => JSON.stringify(item))
.join('\n'),
);
mistralAiNock.get('/v1/files/output-file-2/content').reply(200, batchResult[2]);
mistralAiNock.delete('/v1/files/output-file-1').reply(200);
mistralAiNock.delete('/v1/files/output-file-2').reply(200);
});
afterAll(() => mistralAiNock.done());
new NodeTestHarness({
additionalPackagePaths: [path.join(__dirname, '../../../../@n8n/nodes-langchain')],
}).setupTests({
credentials,
workflowFiles: ['workflow.json'],
});
});
});