modelmix 3.8.2 → 3.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +5 -1
- package/README.md +3 -2
- package/demo/demo.mjs +8 -8
- package/demo/images.mjs +9 -0
- package/demo/img.png +0 -0
- package/index.js +66 -17
- package/package.json +20 -6
- package/test/README.md +158 -0
- package/test/bottleneck.test.js +483 -0
- package/test/fallback.test.js +387 -0
- package/test/fixtures/data.json +36 -0
- package/test/fixtures/img.png +0 -0
- package/test/fixtures/template.txt +15 -0
- package/test/images.test.js +87 -0
- package/test/json.test.js +295 -0
- package/test/live.test.js +356 -0
- package/test/mocha.opts +5 -0
- package/test/setup.js +176 -0
- package/test/templates.test.js +473 -0
- package/test/test-runner.js +73 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
const { expect } = require('chai');
|
|
2
|
+
const sinon = require('sinon');
|
|
3
|
+
const nock = require('nock');
|
|
4
|
+
const { ModelMix } = require('../index.js');
|
|
5
|
+
const generateJsonSchema = require('../schema.js');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
describe('JSON Schema and Structured Output Tests', () => {
|
|
9
|
+
|
|
10
|
+
// Setup test hooks
|
|
11
|
+
if (global.setupTestHooks) {
|
|
12
|
+
global.setupTestHooks();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
nock.cleanAll();
|
|
17
|
+
sinon.restore();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('JSON Schema Generation', () => {
|
|
21
|
+
it('should generate schema for simple object', () => {
|
|
22
|
+
const example = {
|
|
23
|
+
name: 'Alice',
|
|
24
|
+
age: 30,
|
|
25
|
+
isAdmin: false
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const schema = generateJsonSchema(example);
|
|
29
|
+
|
|
30
|
+
expect(schema).to.deep.equal({
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
name: { type: 'string' },
|
|
34
|
+
age: { type: 'integer' },
|
|
35
|
+
isAdmin: { type: 'boolean' }
|
|
36
|
+
},
|
|
37
|
+
required: ['name', 'age', 'isAdmin']
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should generate schema with email format detection', () => {
|
|
42
|
+
const example = { email: 'test@example.com' };
|
|
43
|
+
const schema = generateJsonSchema(example);
|
|
44
|
+
|
|
45
|
+
expect(schema.properties.email).to.deep.equal({
|
|
46
|
+
type: 'string',
|
|
47
|
+
format: 'email'
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should generate schema with date format detection', () => {
|
|
52
|
+
const example = { birthDate: '1990-01-01' };
|
|
53
|
+
const schema = generateJsonSchema(example);
|
|
54
|
+
|
|
55
|
+
expect(schema.properties.birthDate).to.deep.equal({
|
|
56
|
+
type: 'string',
|
|
57
|
+
format: 'date',
|
|
58
|
+
description: 'Date in format YYYY-MM-DD'
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should generate schema with time format detection', () => {
|
|
63
|
+
const example = {
|
|
64
|
+
time1: '14:30',
|
|
65
|
+
time2: '09:15:45'
|
|
66
|
+
};
|
|
67
|
+
const schema = generateJsonSchema(example);
|
|
68
|
+
|
|
69
|
+
expect(schema.properties.time1).to.deep.equal({
|
|
70
|
+
type: 'string',
|
|
71
|
+
format: 'time',
|
|
72
|
+
description: 'Time in format HH:MM'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(schema.properties.time2).to.deep.equal({
|
|
76
|
+
type: 'string',
|
|
77
|
+
format: 'time',
|
|
78
|
+
description: 'Time in format HH:MM:SS'
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should handle nested objects', () => {
|
|
83
|
+
const example = {
|
|
84
|
+
user: {
|
|
85
|
+
name: 'Bob',
|
|
86
|
+
preferences: {
|
|
87
|
+
theme: 'dark'
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const schema = generateJsonSchema(example);
|
|
93
|
+
|
|
94
|
+
expect(schema.properties.user.type).to.equal('object');
|
|
95
|
+
expect(schema.properties.user.properties.name).to.deep.equal({ type: 'string' });
|
|
96
|
+
expect(schema.properties.user.properties.preferences.type).to.equal('object');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle arrays of objects', () => {
|
|
100
|
+
const example = {
|
|
101
|
+
users: [{
|
|
102
|
+
name: 'Alice',
|
|
103
|
+
age: 30
|
|
104
|
+
}]
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const schema = generateJsonSchema(example);
|
|
108
|
+
|
|
109
|
+
expect(schema.properties.users).to.deep.equal({
|
|
110
|
+
type: 'array',
|
|
111
|
+
items: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
name: { type: 'string' },
|
|
115
|
+
age: { type: 'integer' }
|
|
116
|
+
},
|
|
117
|
+
required: ['name', 'age']
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should handle arrays of primitives', () => {
|
|
123
|
+
const example = {
|
|
124
|
+
tags: ['admin', 'user'],
|
|
125
|
+
numbers: [1, 2, 3]
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const schema = generateJsonSchema(example);
|
|
129
|
+
|
|
130
|
+
expect(schema.properties.tags).to.deep.equal({
|
|
131
|
+
type: 'array',
|
|
132
|
+
items: { type: 'string' }
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(schema.properties.numbers).to.deep.equal({
|
|
136
|
+
type: 'array',
|
|
137
|
+
items: { type: 'integer' }
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should handle custom descriptions', () => {
|
|
142
|
+
const example = { name: 'Alice', age: 30 };
|
|
143
|
+
const descriptions = {
|
|
144
|
+
name: 'Full name of the user',
|
|
145
|
+
age: 'User age in years'
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const schema = generateJsonSchema(example, descriptions);
|
|
149
|
+
|
|
150
|
+
expect(schema.properties.name.description).to.equal('Full name of the user');
|
|
151
|
+
expect(schema.properties.age.description).to.equal('User age in years');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle null values', () => {
|
|
155
|
+
const example = { data: null };
|
|
156
|
+
const schema = generateJsonSchema(example);
|
|
157
|
+
|
|
158
|
+
expect(schema.properties.data).to.deep.equal({ type: 'null' });
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should distinguish between integer and float', () => {
|
|
162
|
+
const example = {
|
|
163
|
+
count: 42,
|
|
164
|
+
price: 19.99
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const schema = generateJsonSchema(example);
|
|
168
|
+
|
|
169
|
+
expect(schema.properties.count).to.deep.equal({ type: 'integer' });
|
|
170
|
+
expect(schema.properties.price).to.deep.equal({ type: 'number' });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should handle empty arrays', () => {
|
|
174
|
+
const example = { items: [] };
|
|
175
|
+
const schema = generateJsonSchema(example);
|
|
176
|
+
|
|
177
|
+
expect(schema.properties.items).to.deep.equal({
|
|
178
|
+
type: 'array',
|
|
179
|
+
items: {}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('ModelMix JSON Output', () => {
|
|
185
|
+
let model;
|
|
186
|
+
|
|
187
|
+
beforeEach(() => {
|
|
188
|
+
model = ModelMix.new({
|
|
189
|
+
config: { debug: false }
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should prepare JSON schema for structured output', async () => {
|
|
194
|
+
const example = {
|
|
195
|
+
countries: [{
|
|
196
|
+
name: 'France',
|
|
197
|
+
capital: 'Paris'
|
|
198
|
+
}]
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
model.gpt4o().addText('List 3 countries');
|
|
202
|
+
|
|
203
|
+
// Mock the API response
|
|
204
|
+
nock('https://api.openai.com')
|
|
205
|
+
.post('/v1/chat/completions')
|
|
206
|
+
.reply(200, {
|
|
207
|
+
choices: [{
|
|
208
|
+
message: {
|
|
209
|
+
role: 'assistant',
|
|
210
|
+
content: JSON.stringify({
|
|
211
|
+
countries: [
|
|
212
|
+
{ name: 'France', capital: 'Paris' },
|
|
213
|
+
{ name: 'Germany', capital: 'Berlin' },
|
|
214
|
+
{ name: 'Spain', capital: 'Madrid' }
|
|
215
|
+
]
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
}]
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const result = await model.json(example);
|
|
222
|
+
|
|
223
|
+
expect(result).to.have.property('countries');
|
|
224
|
+
expect(result.countries).to.be.an('array');
|
|
225
|
+
expect(result.countries).to.have.length(3);
|
|
226
|
+
expect(result.countries[0]).to.have.property('name');
|
|
227
|
+
expect(result.countries[0]).to.have.property('capital');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should handle complex nested JSON schema', async () => {
|
|
231
|
+
const example = {
|
|
232
|
+
user: {
|
|
233
|
+
personal: {
|
|
234
|
+
name: 'John',
|
|
235
|
+
age: 25
|
|
236
|
+
},
|
|
237
|
+
contact: {
|
|
238
|
+
email: 'john@example.com',
|
|
239
|
+
phone: '123-456-7890'
|
|
240
|
+
},
|
|
241
|
+
preferences: {
|
|
242
|
+
notifications: true,
|
|
243
|
+
theme: 'dark'
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
metadata: {
|
|
247
|
+
createdAt: '2023-01-01',
|
|
248
|
+
tags: ['premium', 'verified']
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
model.sonnet4().addText('Generate user data');
|
|
253
|
+
|
|
254
|
+
// Mock the API response
|
|
255
|
+
nock('https://api.anthropic.com')
|
|
256
|
+
.post('/v1/messages')
|
|
257
|
+
.reply(200, {
|
|
258
|
+
content: [{
|
|
259
|
+
type: 'text',
|
|
260
|
+
text: JSON.stringify(example)
|
|
261
|
+
}]
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const result = await model.json(example);
|
|
265
|
+
|
|
266
|
+
expect(result.user.personal.name).to.equal('John');
|
|
267
|
+
expect(result.user.contact.email).to.equal('john@example.com');
|
|
268
|
+
expect(result.metadata.tags).to.be.an('array');
|
|
269
|
+
expect(result.metadata.tags).to.deep.equal(['premium', 'verified']);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should handle JSON parsing errors gracefully', async () => {
|
|
273
|
+
model.gpt4o().addText('Generate invalid JSON');
|
|
274
|
+
|
|
275
|
+
// Mock invalid JSON response
|
|
276
|
+
nock('https://api.openai.com')
|
|
277
|
+
.post('/v1/chat/completions')
|
|
278
|
+
.reply(200, {
|
|
279
|
+
choices: [{
|
|
280
|
+
message: {
|
|
281
|
+
role: 'assistant',
|
|
282
|
+
content: 'This is not valid JSON'
|
|
283
|
+
}
|
|
284
|
+
}]
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
await model.json({ name: 'test' });
|
|
289
|
+
expect.fail('Should have thrown an error');
|
|
290
|
+
} catch (error) {
|
|
291
|
+
expect(error.message).to.include('JSON');
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
const { expect } = require('chai');
|
|
2
|
+
const { ModelMix, MixOpenAI, MixAnthropic, MixGoogle } = require('../index.js');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fixturesPath = path.join(__dirname, 'fixtures');
|
|
5
|
+
|
|
6
|
+
const blueSquareBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAMAAABHPGVmAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDkuMS1jMDAzIDc5Ljk2OTBhODdmYywgMjAyNS8wMy8wNi0yMDo1MDoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI2LjkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REM2QzQ3NEQ2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REM2QzQ3NEU2Q0I5MTFGMDlBRTVGQzcwQjMyMkY4MDciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpEQzZDNDc0QjZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpEQzZDNDc0QzZDQjkxMUYwOUFFNUZDNzBCMzIyRjgwNyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvArxh0AAAAGUExURQAA/wAAAHtivz4AAAAjSURBVHja7MGBAAAAAMOg+VNf4QBVAQAAAAAAAAAAAI8JMAAndAABi7SX2gAAAABJRU5ErkJggg==';
|
|
7
|
+
|
|
8
|
+
const setup = {
|
|
9
|
+
options: { temperature: 0 },
|
|
10
|
+
config: { debug: false, max_history: 2 }
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('Live Integration Tests', function () {
|
|
14
|
+
// Increase timeout for real API calls
|
|
15
|
+
this.timeout(30000);
|
|
16
|
+
|
|
17
|
+
describe('Image Processing', function () {
|
|
18
|
+
|
|
19
|
+
it('should process images with OpenAI GPT-5nano', async function () {
|
|
20
|
+
const model = ModelMix.new(setup).gpt5nano();
|
|
21
|
+
|
|
22
|
+
model.addImageFromUrl(blueSquareBase64)
|
|
23
|
+
.addText('What color is this image? Answer in one word only.');
|
|
24
|
+
|
|
25
|
+
const response = await model.message();
|
|
26
|
+
|
|
27
|
+
console.log(`OpenAI GPT-4o response: ${response}`);
|
|
28
|
+
|
|
29
|
+
expect(response).to.be.a('string');
|
|
30
|
+
expect(response.toLowerCase()).to.include('blue');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should process images with Anthropic Claude', async function () {
|
|
34
|
+
const model = ModelMix.new(setup).sonnet35();
|
|
35
|
+
|
|
36
|
+
model.addImageFromUrl(blueSquareBase64)
|
|
37
|
+
.addText('What color is this image? Answer in one word only.');
|
|
38
|
+
|
|
39
|
+
const response = await model.message();
|
|
40
|
+
console.log(`Anthropic Claude response: ${response}`);
|
|
41
|
+
|
|
42
|
+
expect(response).to.be.a('string');
|
|
43
|
+
expect(response.toLowerCase()).to.include('blue');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should process images with Google Gemini', async function () {
|
|
47
|
+
const model = ModelMix.new(setup).gemini25flash();
|
|
48
|
+
|
|
49
|
+
model.addImageFromUrl(blueSquareBase64)
|
|
50
|
+
.addText('What color is this image? Answer in one word only.');
|
|
51
|
+
|
|
52
|
+
const response = await model.message();
|
|
53
|
+
console.log(`Google Gemini response: ${response}`);
|
|
54
|
+
|
|
55
|
+
expect(response).to.be.a('string');
|
|
56
|
+
expect(response.toLowerCase()).to.include('blue');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('JSON Structured Output', function () {
|
|
62
|
+
|
|
63
|
+
it('should return structured JSON with OpenAI', async function () {
|
|
64
|
+
const model = ModelMix.new(setup).gpt5mini();
|
|
65
|
+
|
|
66
|
+
model.addText('Generate information about a fictional character.');
|
|
67
|
+
|
|
68
|
+
const result = await model.json({
|
|
69
|
+
name: "John Doe",
|
|
70
|
+
age: 30,
|
|
71
|
+
occupation: "Engineer",
|
|
72
|
+
skills: ["JavaScript", "Python"]
|
|
73
|
+
}, {}, { addNote: true });
|
|
74
|
+
|
|
75
|
+
console.log(`OpenAI JSON result:`, result);
|
|
76
|
+
|
|
77
|
+
expect(result).to.be.an('object');
|
|
78
|
+
expect(result).to.have.property('name');
|
|
79
|
+
expect(result).to.have.property('age');
|
|
80
|
+
expect(result).to.have.property('occupation');
|
|
81
|
+
expect(result).to.have.property('skills');
|
|
82
|
+
expect(result.skills).to.be.an('array');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should return structured JSON with Sonnet Think', async function () {
|
|
86
|
+
const model = ModelMix.new(setup).sonnet4think();
|
|
87
|
+
|
|
88
|
+
model.addText('Generate information about a fictional city.');
|
|
89
|
+
|
|
90
|
+
const result = await model.json({
|
|
91
|
+
name: "Springfield",
|
|
92
|
+
population: 50000,
|
|
93
|
+
country: "USA",
|
|
94
|
+
attractions: ["Museum", "Park"]
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
console.log(`Anthropic JSON result:`, result);
|
|
98
|
+
|
|
99
|
+
expect(result).to.be.an('object');
|
|
100
|
+
expect(result).to.have.property('name');
|
|
101
|
+
expect(result).to.have.property('population');
|
|
102
|
+
expect(result).to.have.property('country');
|
|
103
|
+
expect(result).to.have.property('attractions');
|
|
104
|
+
expect(result.attractions).to.be.an('array');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should return structured JSON with Google Gemini', async function () {
|
|
108
|
+
const model = ModelMix.new(setup).gemini25flash();
|
|
109
|
+
|
|
110
|
+
model.addText('Generate information about a fictional city.');
|
|
111
|
+
|
|
112
|
+
const result = await model.json({
|
|
113
|
+
name: "Springfield",
|
|
114
|
+
population: 50000,
|
|
115
|
+
country: "USA",
|
|
116
|
+
attractions: ["Museum", "Park"]
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
console.log(`Google Gemini JSON result:`, result);
|
|
120
|
+
|
|
121
|
+
expect(result).to.be.an('object');
|
|
122
|
+
expect(result).to.have.property('name');
|
|
123
|
+
expect(result).to.have.property('population');
|
|
124
|
+
expect(result).to.have.property('country');
|
|
125
|
+
expect(result).to.have.property('attractions');
|
|
126
|
+
expect(result.attractions).to.be.an('array');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('Model Fallback', function () {
|
|
133
|
+
|
|
134
|
+
it('should fallback from OpenAI to Anthropic', async function () {
|
|
135
|
+
// Create a model chain: non-existent model -> Claude
|
|
136
|
+
const model = ModelMix.new(setup)
|
|
137
|
+
.attach('non-existent-model', new MixOpenAI())
|
|
138
|
+
.sonnet4();
|
|
139
|
+
|
|
140
|
+
model.addText('Say "fallback test successful" and nothing else.');
|
|
141
|
+
|
|
142
|
+
const response = await model.message();
|
|
143
|
+
console.log(`Fallback test result: ${response}`);
|
|
144
|
+
|
|
145
|
+
expect(response).to.be.a('string');
|
|
146
|
+
expect(response.toLowerCase()).to.include('fallback test successful');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should fallback from Anthropic to OpenAI', async function () {
|
|
150
|
+
// Create a model chain: non-existent model -> Claude
|
|
151
|
+
const model = ModelMix.new(setup)
|
|
152
|
+
.attach('non-existent-model', new MixAnthropic())
|
|
153
|
+
.gpt41nano();
|
|
154
|
+
|
|
155
|
+
model.addText('Say "fallback test successful" and nothing else.');
|
|
156
|
+
|
|
157
|
+
const response = await model.message();
|
|
158
|
+
console.log(`Fallback test result: ${response}`);
|
|
159
|
+
|
|
160
|
+
expect(response).to.be.a('string');
|
|
161
|
+
expect(response.toLowerCase()).to.include('fallback test successful');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('Additional Model Tests', function () {
|
|
167
|
+
|
|
168
|
+
it('should work with Scout model', async function () {
|
|
169
|
+
const model = ModelMix.new(setup).scout();
|
|
170
|
+
|
|
171
|
+
model.addText('Say "scout test successful" and nothing else.');
|
|
172
|
+
|
|
173
|
+
const response = await model.message();
|
|
174
|
+
console.log(`Scout response: ${response}`);
|
|
175
|
+
|
|
176
|
+
expect(response).to.be.a('string');
|
|
177
|
+
expect(response.toLowerCase()).to.include('scout test successful');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should work with KimiK2 model', async function () {
|
|
181
|
+
const model = ModelMix.new(setup).kimiK2();
|
|
182
|
+
|
|
183
|
+
model.addText('Say "kimik2 test successful" and nothing else.');
|
|
184
|
+
|
|
185
|
+
const response = await model.message();
|
|
186
|
+
console.log(`KimiK2 response: ${response}`);
|
|
187
|
+
|
|
188
|
+
expect(response).to.be.a('string');
|
|
189
|
+
expect(response.toLowerCase()).to.include('kimik2 test successful');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should work with GPT-OSS model', async function () {
|
|
193
|
+
const model = ModelMix.new(setup).gptOss();
|
|
194
|
+
|
|
195
|
+
model.addText('Say "gptoss test successful" and nothing else.');
|
|
196
|
+
|
|
197
|
+
const response = await model.message();
|
|
198
|
+
console.log(`GPT-OSS response: ${response}`);
|
|
199
|
+
|
|
200
|
+
expect(response).to.be.a('string');
|
|
201
|
+
expect(response.toLowerCase()).to.include('gptoss test successful');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should work with Grok3Mini model', async function () {
|
|
205
|
+
const model = ModelMix.new(setup).grok3mini();
|
|
206
|
+
|
|
207
|
+
model.addText('Say "grok3mini test successful" and nothing else.');
|
|
208
|
+
|
|
209
|
+
const response = await model.message();
|
|
210
|
+
console.log(`Grok3Mini response: ${response}`);
|
|
211
|
+
|
|
212
|
+
expect(response).to.be.a('string');
|
|
213
|
+
expect(response.toLowerCase()).to.include('grok3mini test successful');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('Image Processing with JSON Output', function () {
|
|
219
|
+
|
|
220
|
+
it('should process images and return JSON with Maverick', async function () {
|
|
221
|
+
const model = ModelMix.new(setup).maverick();
|
|
222
|
+
model.addImage(path.join(fixturesPath, 'img.png'))
|
|
223
|
+
.addText('Analyze this image and provide details in JSON format.');
|
|
224
|
+
|
|
225
|
+
const result = await model.json({
|
|
226
|
+
color: "string",
|
|
227
|
+
shape: "string",
|
|
228
|
+
description: "string"
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
console.log(`Scout image JSON result:`, result);
|
|
232
|
+
|
|
233
|
+
expect(result).to.be.an('object');
|
|
234
|
+
expect(result).to.have.property('color');
|
|
235
|
+
expect(result).to.have.property('shape');
|
|
236
|
+
expect(result).to.have.property('description');
|
|
237
|
+
expect(result.color.toLowerCase()).to.include('blue');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should process images and return JSON with Grok 4', async function () {
|
|
241
|
+
const model = ModelMix.new(setup).grok4();
|
|
242
|
+
|
|
243
|
+
model.addImageFromUrl(blueSquareBase64)
|
|
244
|
+
.addText('Analyze this image and provide details in JSON format.');
|
|
245
|
+
|
|
246
|
+
const result = await model.json({
|
|
247
|
+
color: "string",
|
|
248
|
+
shape: "string",
|
|
249
|
+
description: "string"
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
console.log(`Grok3Mini image JSON result:`, result);
|
|
253
|
+
|
|
254
|
+
expect(result).to.be.an('object');
|
|
255
|
+
expect(result).to.have.property('color');
|
|
256
|
+
expect(result).to.have.property('shape');
|
|
257
|
+
expect(result).to.have.property('description');
|
|
258
|
+
expect(result.color.toLowerCase()).to.include('blue');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('JSON Structured Output for New Models', function () {
|
|
264
|
+
|
|
265
|
+
it('should return structured JSON with Scout', async function () {
|
|
266
|
+
const model = ModelMix.new(setup).scout();
|
|
267
|
+
|
|
268
|
+
model.addText('Generate information about a fictional animal.');
|
|
269
|
+
|
|
270
|
+
const result = await model.json({
|
|
271
|
+
name: "Dragon",
|
|
272
|
+
type: "Mythical",
|
|
273
|
+
abilities: ["Fire breathing", "Flight"],
|
|
274
|
+
habitat: "Mountains"
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
console.log(`Scout JSON result:`, result);
|
|
278
|
+
|
|
279
|
+
expect(result).to.be.an('object');
|
|
280
|
+
expect(result).to.have.property('name');
|
|
281
|
+
expect(result).to.have.property('type');
|
|
282
|
+
expect(result).to.have.property('abilities');
|
|
283
|
+
expect(result).to.have.property('habitat');
|
|
284
|
+
expect(result.abilities).to.be.an('array');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should return structured JSON with KimiK2', async function () {
|
|
288
|
+
const model = ModelMix.new(setup).kimiK2();
|
|
289
|
+
|
|
290
|
+
model.addText('Generate information about a fictional vehicle.');
|
|
291
|
+
|
|
292
|
+
const result = await model.json({
|
|
293
|
+
name: "Flying Car",
|
|
294
|
+
type: "Transportation",
|
|
295
|
+
features: ["Anti-gravity", "Auto-pilot"],
|
|
296
|
+
manufacturer: "Future Motors"
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
console.log(`KimiK2 JSON result:`, result);
|
|
300
|
+
|
|
301
|
+
expect(result).to.be.an('object');
|
|
302
|
+
expect(result).to.have.property('name');
|
|
303
|
+
expect(result).to.have.property('type');
|
|
304
|
+
expect(result).to.have.property('features');
|
|
305
|
+
expect(result).to.have.property('manufacturer');
|
|
306
|
+
expect(result.features).to.be.an('array');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should return structured JSON with GPT-OSS', async function () {
|
|
310
|
+
const model = ModelMix.new(setup).gptOss();
|
|
311
|
+
|
|
312
|
+
model.addText('Generate information about a fictional planet.');
|
|
313
|
+
|
|
314
|
+
const result = await model.json({
|
|
315
|
+
name: "Nova Prime",
|
|
316
|
+
type: "Gas Giant",
|
|
317
|
+
moons: ["Alpha", "Beta", "Gamma"],
|
|
318
|
+
atmosphere: "Hydrogen and Helium"
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
console.log(`GPT-OSS JSON result:`, result);
|
|
322
|
+
|
|
323
|
+
expect(result).to.be.an('object');
|
|
324
|
+
expect(result).to.have.property('name');
|
|
325
|
+
expect(result).to.have.property('type');
|
|
326
|
+
expect(result).to.have.property('moons');
|
|
327
|
+
expect(result).to.have.property('atmosphere');
|
|
328
|
+
expect(result.moons).to.be.an('array');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should return structured JSON with Grok3Mini', async function () {
|
|
332
|
+
const model = ModelMix.new(setup).grok3mini();
|
|
333
|
+
|
|
334
|
+
model.addText('Generate information about a fictional technology.');
|
|
335
|
+
|
|
336
|
+
const result = await model.json({
|
|
337
|
+
name: "Quantum Computer",
|
|
338
|
+
type: "Computing",
|
|
339
|
+
applications: ["Cryptography", "Simulation"],
|
|
340
|
+
power: "1000 qubits"
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
console.log(`Grok3Mini JSON result:`, result);
|
|
344
|
+
|
|
345
|
+
expect(result).to.be.an('object');
|
|
346
|
+
expect(result).to.have.property('name');
|
|
347
|
+
expect(result).to.have.property('type');
|
|
348
|
+
expect(result).to.have.property('applications');
|
|
349
|
+
expect(result).to.have.property('power');
|
|
350
|
+
expect(result.applications).to.be.an('array');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
});
|