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:
@@ -0,0 +1,680 @@
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
import * as helpers from '@utils/helpers';
|
||||
|
||||
import * as image from './actions/image';
|
||||
import * as text from './actions/text';
|
||||
import * as transport from './transport';
|
||||
import type { OllamaChatResponse, OllamaMessage } from './helpers/interfaces';
|
||||
|
||||
describe('Ollama Node', () => {
|
||||
const executeFunctionsMock = mockDeep<IExecuteFunctions>();
|
||||
const apiRequestMock = jest.spyOn(transport, 'apiRequest');
|
||||
const getConnectedToolsMock = jest.spyOn(helpers, 'getConnectedTools');
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('Text -> Message', () => {
|
||||
it('should call the API with correct parameters for basic message', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llama3.2:latest';
|
||||
case 'messages.values':
|
||||
return [{ role: 'user', content: 'Hello, world!' }];
|
||||
case 'simplify':
|
||||
return true;
|
||||
case 'options':
|
||||
return {
|
||||
system: 'You are a helpful assistant.',
|
||||
temperature: 0.7,
|
||||
top_p: 0.9,
|
||||
top_k: 40,
|
||||
num_predict: 1024,
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
executeFunctionsMock.getNodeInputs.mockReturnValue([{ type: 'main' }]);
|
||||
getConnectedToolsMock.mockResolvedValue([]);
|
||||
apiRequestMock.mockResolvedValue({
|
||||
model: 'llama3.2:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: { role: 'assistant', content: 'Hello! How can I help you today?' },
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
const result = await text.message.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: { content: 'Hello! How can I help you today?' },
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
]);
|
||||
expect(apiRequestMock).toHaveBeenCalledWith('POST', '/api/chat', {
|
||||
body: {
|
||||
model: 'llama3.2:latest',
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||
{ role: 'user', content: 'Hello, world!' },
|
||||
],
|
||||
stream: false,
|
||||
tools: [],
|
||||
options: {
|
||||
temperature: 0.7,
|
||||
top_p: 0.9,
|
||||
top_k: 40,
|
||||
num_predict: 1024,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return full response when simplify is false', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llama3.2:latest';
|
||||
case 'messages.values':
|
||||
return [{ role: 'user', content: 'Test message' }];
|
||||
case 'simplify':
|
||||
return false;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
executeFunctionsMock.getNodeInputs.mockReturnValue([{ type: 'main' }]);
|
||||
getConnectedToolsMock.mockResolvedValue([]);
|
||||
const mockResponse = {
|
||||
model: 'llama3.2:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: { role: 'assistant', content: 'Test response' },
|
||||
done: true,
|
||||
total_duration: 5000000,
|
||||
load_duration: 1000000,
|
||||
eval_count: 10,
|
||||
eval_duration: 2000000,
|
||||
} as OllamaChatResponse;
|
||||
apiRequestMock.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await text.message.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: mockResponse,
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle tool calls correctly', async () => {
|
||||
const mockTool = {
|
||||
name: 'calculator',
|
||||
description: 'Performs calculations',
|
||||
schema: z.object({
|
||||
expression: z.string().describe('Mathematical expression to evaluate'),
|
||||
}),
|
||||
invoke: jest.fn().mockResolvedValue({ result: 42 }),
|
||||
};
|
||||
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llama3.2:latest';
|
||||
case 'messages.values':
|
||||
return [{ role: 'user', content: 'What is 6 * 7?' }];
|
||||
case 'simplify':
|
||||
return true;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
executeFunctionsMock.getNodeInputs.mockReturnValue([{ type: 'main' }, { type: 'ai_tool' }]);
|
||||
// @ts-expect-error: Mocking a tool, we do not implement the full interface
|
||||
getConnectedToolsMock.mockResolvedValue([mockTool]);
|
||||
|
||||
apiRequestMock.mockResolvedValueOnce({
|
||||
model: 'llama3.2:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
tool_calls: [
|
||||
{
|
||||
function: {
|
||||
name: 'calculator',
|
||||
arguments: { expression: '6 * 7' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
apiRequestMock.mockResolvedValueOnce({
|
||||
model: 'llama3.2:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: { role: 'assistant', content: 'The result is 42.' },
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
const result = await text.message.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: { content: 'The result is 42.' },
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
]);
|
||||
expect(mockTool.invoke).toHaveBeenCalledWith({ expression: '6 * 7' });
|
||||
expect(apiRequestMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should handle tool execution errors gracefully', async () => {
|
||||
const mockTool = {
|
||||
name: 'failing_tool',
|
||||
description: 'A tool that fails',
|
||||
schema: z.object({}),
|
||||
invoke: jest.fn().mockRejectedValue(new Error('Tool execution failed')),
|
||||
};
|
||||
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llama3.2:latest';
|
||||
case 'messages.values':
|
||||
return [{ role: 'user', content: 'Use the failing tool' }];
|
||||
case 'simplify':
|
||||
return true;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
executeFunctionsMock.getNodeInputs.mockReturnValue([{ type: 'main' }, { type: 'ai_tool' }]);
|
||||
// @ts-expect-error: Mocking a tool, we do not implement the full interface
|
||||
getConnectedToolsMock.mockResolvedValue([mockTool]);
|
||||
|
||||
apiRequestMock.mockResolvedValueOnce({
|
||||
model: 'llama3.2:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
tool_calls: [
|
||||
{
|
||||
function: {
|
||||
name: 'failing_tool',
|
||||
arguments: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
apiRequestMock.mockResolvedValueOnce({
|
||||
model: 'llama3.2:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: { role: 'assistant', content: 'I encountered an error with the tool.' },
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
const result = await text.message.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: { content: 'I encountered an error with the tool.' },
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
]);
|
||||
|
||||
const secondCallBody = apiRequestMock.mock.calls[1][2]?.body as any;
|
||||
const toolMessage = secondCallBody.messages.find((msg: OllamaMessage) => msg.role === 'tool');
|
||||
expect(toolMessage.content).toBe('Error executing tool: Tool execution failed');
|
||||
});
|
||||
|
||||
it('should process stop sequences correctly', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llama3.2:latest';
|
||||
case 'messages.values':
|
||||
return [{ role: 'user', content: 'Generate text' }];
|
||||
case 'simplify':
|
||||
return true;
|
||||
case 'options':
|
||||
return {
|
||||
stop: '###,END,STOP',
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
executeFunctionsMock.getNodeInputs.mockReturnValue([{ type: 'main' }]);
|
||||
getConnectedToolsMock.mockResolvedValue([]);
|
||||
apiRequestMock.mockResolvedValue({
|
||||
model: 'llama3.2:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: { role: 'assistant', content: 'Generated text' },
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
await text.message.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(apiRequestMock).toHaveBeenCalledWith('POST', '/api/chat', {
|
||||
body: {
|
||||
model: 'llama3.2:latest',
|
||||
messages: [{ role: 'user', content: 'Generate text' }],
|
||||
stream: false,
|
||||
tools: [],
|
||||
options: {
|
||||
stop: ['###', 'END', 'STOP'],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle various model-specific options', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llama3.2:latest';
|
||||
case 'messages.values':
|
||||
return [{ role: 'user', content: 'Test with options' }];
|
||||
case 'simplify':
|
||||
return true;
|
||||
case 'options':
|
||||
return {
|
||||
temperature: 0.5,
|
||||
top_p: 0.8,
|
||||
top_k: 30,
|
||||
num_predict: 512,
|
||||
frequency_penalty: 0.1,
|
||||
presence_penalty: 0.2,
|
||||
repeat_penalty: 1.2,
|
||||
num_ctx: 2048,
|
||||
repeat_last_n: 32,
|
||||
min_p: 0.1,
|
||||
seed: 123,
|
||||
low_vram: true,
|
||||
main_gpu: 1,
|
||||
num_batch: 256,
|
||||
num_gpu: 2,
|
||||
num_thread: 8,
|
||||
penalize_newline: false,
|
||||
use_mlock: true,
|
||||
use_mmap: false,
|
||||
vocab_only: false,
|
||||
keep_alive: '10m',
|
||||
format: 'json',
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
executeFunctionsMock.getNodeInputs.mockReturnValue([{ type: 'main' }]);
|
||||
getConnectedToolsMock.mockResolvedValue([]);
|
||||
apiRequestMock.mockResolvedValue({
|
||||
model: 'llama3.2:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: { role: 'assistant', content: '{"response": "test"}' },
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
await text.message.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(apiRequestMock).toHaveBeenCalledWith('POST', '/api/chat', {
|
||||
body: {
|
||||
model: 'llama3.2:latest',
|
||||
messages: [{ role: 'user', content: 'Test with options' }],
|
||||
stream: false,
|
||||
tools: [],
|
||||
options: {
|
||||
temperature: 0.5,
|
||||
top_p: 0.8,
|
||||
top_k: 30,
|
||||
num_predict: 512,
|
||||
frequency_penalty: 0.1,
|
||||
presence_penalty: 0.2,
|
||||
repeat_penalty: 1.2,
|
||||
num_ctx: 2048,
|
||||
repeat_last_n: 32,
|
||||
min_p: 0.1,
|
||||
seed: 123,
|
||||
low_vram: true,
|
||||
main_gpu: 1,
|
||||
num_batch: 256,
|
||||
num_gpu: 2,
|
||||
num_thread: 8,
|
||||
penalize_newline: false,
|
||||
use_mlock: true,
|
||||
use_mmap: false,
|
||||
vocab_only: false,
|
||||
keep_alive: '10m',
|
||||
format: 'json',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Image -> Analyze', () => {
|
||||
it('should analyze image from binary data', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llava:latest';
|
||||
case 'inputType':
|
||||
return 'binary';
|
||||
case 'binaryPropertyName':
|
||||
return 'data';
|
||||
case 'text':
|
||||
return "What's in this image?";
|
||||
case 'simplify':
|
||||
return true;
|
||||
case 'options':
|
||||
return {
|
||||
temperature: 0.3,
|
||||
num_predict: 512,
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
executeFunctionsMock.helpers.getBinaryDataBuffer.mockResolvedValue(
|
||||
Buffer.from('test image data'),
|
||||
);
|
||||
apiRequestMock.mockResolvedValue({
|
||||
model: 'llava:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: 'This image shows a beautiful mountain landscape with snow-capped peaks.',
|
||||
},
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
const result = await image.analyze.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: {
|
||||
content: 'This image shows a beautiful mountain landscape with snow-capped peaks.',
|
||||
},
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
]);
|
||||
expect(apiRequestMock).toHaveBeenCalledWith('POST', '/api/chat', {
|
||||
body: {
|
||||
model: 'llava:latest',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: "What's in this image?",
|
||||
images: ['dGVzdCBpbWFnZSBkYXRh'], // base64 encoded 'test image data'
|
||||
},
|
||||
],
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: 0.3,
|
||||
num_predict: 512,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should analyze image from URL', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llava:latest';
|
||||
case 'inputType':
|
||||
return 'url';
|
||||
case 'imageUrls':
|
||||
return 'https://example.com/test-image.jpg';
|
||||
case 'text':
|
||||
return 'Describe this image';
|
||||
case 'simplify':
|
||||
return true;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
executeFunctionsMock.helpers.httpRequest.mockResolvedValue(
|
||||
Buffer.from('downloaded image data'),
|
||||
);
|
||||
apiRequestMock.mockResolvedValue({
|
||||
model: 'llava:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: 'This image contains a sunset over the ocean.',
|
||||
},
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
const result = await image.analyze.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: { content: 'This image contains a sunset over the ocean.' },
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
]);
|
||||
expect(executeFunctionsMock.helpers.httpRequest).toHaveBeenCalledWith({
|
||||
method: 'GET',
|
||||
url: 'https://example.com/test-image.jpg',
|
||||
encoding: 'arraybuffer',
|
||||
});
|
||||
expect(apiRequestMock).toHaveBeenCalledWith('POST', '/api/chat', {
|
||||
body: {
|
||||
model: 'llava:latest',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Describe this image',
|
||||
images: ['ZG93bmxvYWRlZCBpbWFnZSBkYXRh'], // base64 encoded 'downloaded image data'
|
||||
},
|
||||
],
|
||||
stream: false,
|
||||
options: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple images from URLs', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llava:latest';
|
||||
case 'inputType':
|
||||
return 'url';
|
||||
case 'imageUrls':
|
||||
return 'https://example.com/image1.jpg, https://example.com/image2.png';
|
||||
case 'text':
|
||||
return 'Compare these images';
|
||||
case 'simplify':
|
||||
return true;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
executeFunctionsMock.helpers.httpRequest.mockResolvedValueOnce(
|
||||
Buffer.from('first image data'),
|
||||
);
|
||||
executeFunctionsMock.helpers.httpRequest.mockResolvedValueOnce(
|
||||
Buffer.from('second image data'),
|
||||
);
|
||||
apiRequestMock.mockResolvedValue({
|
||||
model: 'llava:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: 'Both images show different landscapes.',
|
||||
},
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
const result = await image.analyze.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: { content: 'Both images show different landscapes.' },
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
]);
|
||||
expect(executeFunctionsMock.helpers.httpRequest).toHaveBeenCalledTimes(2);
|
||||
expect(apiRequestMock).toHaveBeenCalledWith('POST', '/api/chat', {
|
||||
body: {
|
||||
model: 'llava:latest',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Compare these images',
|
||||
images: [
|
||||
'Zmlyc3QgaW1hZ2UgZGF0YQ==', // base64 encoded 'first image data'
|
||||
'c2Vjb25kIGltYWdlIGRhdGE=', // base64 encoded 'second image data'
|
||||
],
|
||||
},
|
||||
],
|
||||
stream: false,
|
||||
options: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple binary images', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llava:latest';
|
||||
case 'inputType':
|
||||
return 'binary';
|
||||
case 'binaryPropertyName':
|
||||
return 'image1,image2';
|
||||
case 'text':
|
||||
return 'Analyze these images';
|
||||
case 'simplify':
|
||||
return false;
|
||||
case 'options':
|
||||
return {};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
executeFunctionsMock.helpers.getBinaryDataBuffer.mockResolvedValueOnce(
|
||||
Buffer.from('first binary image'),
|
||||
);
|
||||
executeFunctionsMock.helpers.getBinaryDataBuffer.mockResolvedValueOnce(
|
||||
Buffer.from('second binary image'),
|
||||
);
|
||||
const mockResponse = {
|
||||
model: 'llava:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: 'Analysis complete for both images.',
|
||||
},
|
||||
done: true,
|
||||
eval_count: 25,
|
||||
eval_duration: 3000000,
|
||||
} as OllamaChatResponse;
|
||||
apiRequestMock.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await image.analyze.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
json: mockResponse,
|
||||
pairedItem: { item: 0 },
|
||||
},
|
||||
]);
|
||||
expect(executeFunctionsMock.helpers.getBinaryDataBuffer).toHaveBeenCalledTimes(2);
|
||||
expect(executeFunctionsMock.helpers.getBinaryDataBuffer).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
0,
|
||||
'image1',
|
||||
);
|
||||
expect(executeFunctionsMock.helpers.getBinaryDataBuffer).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
0,
|
||||
'image2',
|
||||
);
|
||||
});
|
||||
|
||||
it('should process stop sequences for image analysis', async () => {
|
||||
executeFunctionsMock.getNodeParameter.mockImplementation((parameter: string) => {
|
||||
switch (parameter) {
|
||||
case 'modelId':
|
||||
return 'llava:latest';
|
||||
case 'inputType':
|
||||
return 'binary';
|
||||
case 'binaryPropertyName':
|
||||
return 'data';
|
||||
case 'text':
|
||||
return 'Describe briefly';
|
||||
case 'simplify':
|
||||
return true;
|
||||
case 'options':
|
||||
return {
|
||||
stop: 'END,DONE',
|
||||
temperature: 0.1,
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
|
||||
executeFunctionsMock.helpers.getBinaryDataBuffer.mockResolvedValue(Buffer.from('test image'));
|
||||
apiRequestMock.mockResolvedValue({
|
||||
model: 'llava:latest',
|
||||
created_at: '2023-10-01T10:00:00Z',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: 'A simple image.',
|
||||
},
|
||||
done: true,
|
||||
} as OllamaChatResponse);
|
||||
|
||||
await image.analyze.execute.call(executeFunctionsMock, 0);
|
||||
|
||||
expect(apiRequestMock).toHaveBeenCalledWith('POST', '/api/chat', {
|
||||
body: {
|
||||
model: 'llava:latest',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Describe briefly',
|
||||
images: ['dGVzdCBpbWFnZQ=='], // base64 encoded 'test image'
|
||||
},
|
||||
],
|
||||
stream: false,
|
||||
options: {
|
||||
stop: ['END', 'DONE'],
|
||||
temperature: 0.1,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user